Introduction

Hi ! , all you guys in the xBase community.

I would like to explain the existence of these eNotes to you.

I started creating 'ClickStart' eNotes many years ago (ten ?), and over the years have produced many!

It was when I transferred my coding skills over from VO to Vulcan.NET that I saw the real need

to have material in a CHM format, for me to refer back to when I needed help and guidance on syntax

and style of coding etc.. In fact I found the 'Search' facility most useful in the CHM, and still use it a lot.

I suppose it was simply the size of 'the .NET Universe' which demanded some sort of a quick way for

me to 'get help' from past material.

Although I wrote the eNotes for my own use, and still do use them very regularly, I also made them

available to others in the xBase community. It was sort of the full backup to the Pearls folder in the

xBase Forum. A place where everything should be.

Since the 'Roslyn' open source availability, and the fact that I can now do virtually anything in the new

X# language that can be done in C# and .NET generally, it seems a great idea to pull all of my suitable

working code and support material together in one eVolume. A single CHM file.

Preparing my session material for Cologne 2017 (the German conference in April 2017) I started to build

up a body of working syntax for X#, and at that time it was easiest for me to include all I got working in

a special appendix in the 'ClickStartLINQ' eNotes, that already existed for C# and Cologne 2016.

There is actually quite a bit of support work in this 'Appendix 'X' - see below :-

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

Any section or sub-section labelled as "From the appendix 'X' in ClickStartLINQ" is the earlier

(first) material I put together on X# as I prepared for the 2017 Cologne German IT Conference.

Because all the material was tried and tested and from working applications, I hesitate not

to use it, or have it available for 'check-backs'.

It seems perfectly reasonable for me to make it available to one and all (and me) alongside

the newer material that I am researching and preparing for Cologne 2018.

I will include most of it 'as-is', and only re-write and re-work the most important parts / sections.

Finally, I need to say that my own interest and work over many years, has been centred on WPF

as the form display / presentation and also SQL as the back-up database provision.

So you will see little here (in these eNotes) which relates to WinForms and/or DBF files.

Yes, I did use both of these technologies in the past, and enjoyed doing so. However, progress

is a wonderful thing - so be prepared to tackle and 'embrace' the newest technologies from Microsoft.

I wish you well in learning and using the X# language.

*** Remember *** Use the 'Search' facility whenever you can, it does a good job in finding suitable

material for you to study, copy and adapt to your coding needs.

PHH - April 11th. & 21st December 2017.


Welcome topic

Okay then guys, welcome to the world of X# and in particular that of .NET.

When we use X# we are firmly in the world of .NET and should make sure that we get the most out

of the fact that we have all .NET stuff at our finger tips.

It is VERY tempting to just continue writing code in the style we have become accustomed, rather

than write new code in a style which makes the most from the .NET features and functionality available

to use as developers.

The correct approach in my mind is to research and find out what is already 'out there'. Don't let us

re-invent the wheel (or attempt to), lets get advice on the best way to approach .NET 'stuff'.

From my experience I now realise that the world of .NET is very different to that of VO. Using VO we

all needed to be self sufficient where as with .NET we need to be good at searching and asking the

right questions, so that we get meaningful answers about coding, style and approach.

*** WARNING ***

Above I have gone on at length about NOT doing stuff our own way, but looking for advice etc..

But the problem is there is also a LOT of published articles and the likes which are 'low grade'.

Sometimes it looks as though guys just like to hear the sound of their own voice, (as it were),

and do not worry at all about leading others in the wrong direction.

Just because some code works does NOT say that it is the best way to do something.

I spend a lot of time searching, and then weeding out the poor solutions my searches throw up.

Try to be more discerning in what you try out after searching.

It is NOT easy to try and tell developers how to do this, it can be difficult - but then the nature

of the web / internet lets everyone and anyone shout and have their voice heard ;-0((

PHH - April 11th. 2017


.NET a 'living' History !

Here is a slide I prepared for the German xBase.Live conference in Cologne in April of 2018 :-

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

PHH - 29th. December 2017.


Reference books and other material

I will attempt to add all the book reference details to the child sections from here.

As I open the existing eNotes in 'ClickStart' I will transfer the images and comments.

I have already started by adding those details found at the start of 'ClickStartLINQ'.

Others will follow later - like 'ClickStartXAML', 'ClickStartMVVM', 'ClickStartWPF', and

'ClickStartSQL'.

As the eBook on XSharp progresses, I will add more and more support information here.

Now lets make a start ...............

PHH - April 11th. & 21st. December 2017.


LINQ related ...

I include in this subsection details of the three books and eBook which I used to learn LINQ.

They seem to be serving me very well as currently I am over half way and going along nicely.

The first two publications I got in hard copy format - i.e. 'Books' to you and me. The third one

I got in eBook (PDF) format only, as I find it easier to work on my three screens that way.

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

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

I bought this next book a while back but it is still most useful :-

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

ISBN 13 : 978-0-7356-2391-0

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

Now the eBook format :

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

I got my eBook from O'Reilly - an on-line purchase.

ISBN : 978-0-735-64057-3

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

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

Originally done :- PHH - 16th. August 2012.

PHH - 21st. December 2017.


Navigation: Introduction > Reference books and other material >

Return to chapter overview

MVVM related ...

PHH - 21st. December 2017.


Navigation: Introduction > Reference books and other material >

Return to chapter overview

WPF related ...

PHH - 21st. December 2017.


Navigation: Introduction > Reference books and other material >

Return to chapter overview

SQL related ...

PHH - 21st. December 2017.


Navigation: Introduction > Reference books and other material >

Return to chapter overview

XAML

PHH - 29th. December 2017.

.


QR codes

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

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

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

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

In fact on Amazon there are many such little books see this Thumbnail type display :-

PHH - 15th. February 2018.


.NET API Browser

https://docs.microsoft.com/en-gb/dotnet/api/?view=netframework-4.7.1

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

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

PHH - 5th. March 2018.


X# related web stuff ...

Wolfgang has provided me with a platform to place my eNotes on a section of his server.

Here is what you see when you type in as a URL the following ... "enotes.xsharp.it".

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

Many of the included CHM files have been given a name beginning with "ClickStart", as this was the name created, or

thought up by John Parker some years back.

I have then added unique letters to designate the contents of the eVolume, like 'VMs' for Virtual machines.

Other are reasonably obvious :-

'WPF' for Windows Presentation Foundation

'EF4' for Entity Framework 4 and after

'RS' for Reporting Services

'VS' for Visual Studio

both 'TSQl' and 'SQL' handle Microsoft's SQL Server stuff

Others are more obvious, like ADO, XAML, LINQ, PowerShell, "Reports and Printing", and

there are file entries containing session material from previous events and conferences.

DevShare in England and 'Vulcan-Live' and similar from Germany (Stuttgart and Cologne).

'ClickStartXSharp' is intended to hold the majority of the material related to MOST things coded

in X# and working for a very wide range of topics.

PHH - 8th. March 2018.


C# and what we should do with it

Although the whole of this eBook material and .NET research is about X# (XSharp) and its correct

syntax, we can't ignore Microsoft's flagship language of C#.

In fact one of the BIG things about X# is that it has such a wealth of suitable material on hand to

support it via the internet. There are samples for just about everything, and more besides most probably ;-0)

So we all need to be able to read C# code as well as being able to convert it into X# code for our own purposes.

For this important reason I try to compare C# stuff with that of X# whenever it makes sense to do so.

Also, in my own coding I try to use the syntax options which are most closely related to C#. There seems

to me to be little point in trying to do things the old xBase way when X# has on offer a modern C# .NET

code alternative.

I appreciate that Robert and his TEAM have decided to include a number of C# alternative syntax options,

and to me they would not include anything which does not make sense. So if they include a C# style

syntax option then I will attempt to use it.

All of this (the above) just seems to make good sense I feel.

Now, lets go and learn some new 'stuff' ! ;-0)

PHH - April 11th. & 21st. December 2017.


Visual Studio

Although XSharp guys have a very good interactive development environment (IDE) created by

Chris Pyrgas and written now in X#, I will be using Visual Studio throughout these eNotes.

After using VIDE for two and a half years (loving every minute of it) I had to move over to Visual

Studio in order to start using WPF, the more modern form technology from Microsoft.

For getting towards ten years now I have been using VS to create WPF and XAML apps.

All of this time I have used the built in WPF design surface and editor, have have found it very

useful and productive indeed.

It does help however, if one takes some time to understand how to use the design surface correctly.

I see a lot of 'bad press' in the Forum posts regarding the VS editor for WPF not being any good - well,

this is not true and such posts are obviously written by those with little to no experience of using the

tools.

I have spent literally thousands of hours in the WPF/XAML editor screens and find them most suitable.

AND - I even believe that there are big improvements available in the latest version of Visual Studio.

So don't be put off using WPF for these untrue reasons.

I will say more about the design surface and how to get the best from it, in other sections below here,

and in "GUI forms- WPF" over half way through these eNotes.

PHH - April 11th. & 21st. December 2017.


which version to use

If we take this (the subsection heading title) as a question, then the answer is quite simple.

Always use the latest public release version of VS, as long as it is supported by the TEAM and

the integration of the X# compiler into that version of VS.

By public release I do not mean any beta releases.

Also the term 'Community' version is also different, this just means the version Microsoft is prepared

to allow guys in the general community to use - you know the standard 'man-in-the' street.

I think as professional software guys we should be using VS 'Professional' and pay for it through the

various annual subscription schemes that Microsoft offers to us as developers.

Having said the above, I personally have hit a related snag / issue with the VS 2017 'Installer'.

If you are to install more than one version of VS2017, and also possibly remove one version and replace

it by another - then do take GREAT care!

The installer seems to have been designed to do just what I am warning you about - BUT - it has been

'broken' by someone at MS.

On my powerful Windows 10 desktop platform I am now in a position where the Installer will NOT complete

an install of any sort of VS 2017 - even though the software has been fully removed by MS standard tools.

My way around this was to create and run a Virtual Machine on the desktop.

Now I can install and run VS 2017 within the Virtual Machine environment. Not necessarily ideal, but it works.

So do be careful to get a clean install, and then don't mess with it !

PHH - April 11th. 2017


How to set-up the VS environment

PHH - April 11th. 2017


a Simple 'Trick' (or approach) so as to use VS effectively

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

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

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

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

PHH - 21st. December 2017. & then 8th. March 2018.


Some commonly used 'Windows'

Okay then guys, what do we mean by this title ?

What are 'Window' objects (or what do I mean by windows) and what is 'commonly used' for

developer guys ?

Well, we can keep things simple at the same time as explaining a lot

PHH - April 11th. 2017 & 8th. March 2018.


Taking charge of the Window (panes)

PHH - April 11th. 2017


Navigation: Visual Studio >

Return to chapter overview

What NOT to be scared about

PHH - April 11th. 2017


Navigation: Visual Studio >

Return to chapter overview

some useful tips and tricks

PHH - April 11th. 2017


What is 'Blend' and where does it fit in ?

PHH - April 11th. 2017


X# code syntax

Okay then guys, this whole section on X# code syntax is probably the most important in

all of the eNotes.

We all need a good and reliable reference to be able to view, and copy, working X# code.

Getting the syntax right, and using the most modern (or up to date) style of syntax, is quite

separate from a 'pattern of development'.

The 'pattern of development' a developer uses is quite another matter to the style of the basic

X# code used.

Whenever possible, I will try to use modern style syntax, as well as showing all the alternatives

I can find, to help you guys out.

We need to know them all, even if we just use the modern style in new code.

Also, you need to know and appreciate that any code images of text that I include will always

first have been run in my desktop VS installation.

Yes, only live code along with live display output.

I doubt if any future changes in the X# compiler will cause my existing working code to fail.

PHH - April 11th. & 21st. December 2017.


using the Console for simple test apps ...

PHH - 27th. December 2017.


Case sensitivity - in code and script

PHH - April 12th. 2017


Navigation: X# code syntax >

Return to chapter overview

basic xBase code style

PHH - April 11th. 2017


Declaring and Assigning variables

Simple enough you may say, declaring variables, we have been doing that for years !

However, these days in .NET there are quite a few ways to do this, and we do need to know

each and every way.

Sometimes just so we can read other people's code, but more often these days so we can

more easily write our own code.

The local phrase "Horses for courses" means just that - sometimes its better to do it one way

and sometimes it is better to do it another way.

Lets start with a way that we may already be using - check below :-

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

Or an older (fashioned) way to do similar to above is shown in the following image :-

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

Yes, the two line coding approach does look a bit old fashioned to me ;-0)

So lets return to the first style in order to make a Typed List :-

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

It is when typing the sort of code shown above that we as coders think that there has to be

a 'smarter' way to do this.

I say smarter because 'better' does not really apply to declarations of variables I feel.

Look at the smarter way shown below, when we don't need repeat the 'List<int>' characters :-

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

Now then folks, some of us would say that using ' var ' is the C# way of doing 'implied'.

That may be so - BUT - it is nice that ' var ' actually works for us as well as implied.

Check this out below :-

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

At the time of coding the above lines I did not quite know what the Type of the query result would be.

Not exactly that is, but 'var' works nicely.

When we use code like that shown in the next image it is definitely to save on typing characters

unnecessarily - study this detail :-

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

Now then guys, the next example is definitely needed to be the way it is, simply because we

do not know what type to use ourselves as the Type being used is Anonymous and will be created

by the system at runtime.

Check this out :-

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

Yes, above we could have used 'var', but somehow for a X# guy coming from Vulcan/VO we like

to see 'local' as our eyes pick this up easier when we read the code - perhaps !?

So here is a 'var' variation of the same code, but also with the system generated Type visible :-

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

Okay then, lets move on to a Collection example.

It is VERY common to see LINQ query definitions as of the 'var' syntax.

There are zillions of examples on the internet of C# samples with 'var', so why don't we just stick with it?

Anyway, for guys like Dick there is less typing at the keyboard for 'var' than 'local implied'.

Lets study a simple example of a LINQ query statement with 'var' - see below :-

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

Lets try and improve things by getting the Type from the system - check below :-

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

Oh! - that is interesting, they are the same - so the ToString() actually just supplies the Type of the

returned Collection. Check the output line details.

Lets take this a little further then ..............

LINQ requires us to be much more flexible with our variable definitions than we were in general coding.

There are two reasons for this - the Collection returned type and also the fact that the Collection items,

or elements, may also be Anonymous Types.

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

Handling 'Types' returned from Methods :

Okay then guys it is one thing managing our Types and variable declarations within a single member

or Method, but it is another handling and managing the objects returned from these Methods.

Lets see now a typical LINQ query method, one which returns a results Collection.

Study the following image carefully, especially lines 283 and 294, where the returned type is

defined and the assigned :-

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

This above is by far the easiest to manage as the returned data is based on two standard data Types.

We has a 'List<T>' and a specified defined class. Nothing else more complicated.

Now then, lets see the calling code and how the results are then bound to the DataGrid control in

the WPF form :-

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

Notice that the MessageBox shows the types to be just as I said above in my explanation.

We are nearly done for this sub-section, lets just see a bit more code related to LINQ querying.

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

Above we have a data Collection being returned from a STATIC Method.

Now we need to take a look at the STATIC Method and how the variables are declared within it etc. :-

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

So we have the Method returning a List of Type 'Student', that should be straight forward.

Okay guys, I think we have done this little topic to death ;-0)

Time to move on to the next sub-section.

See you there ...................

PHH - 12th. December 2017.


some new X# options, similar to C#

Robert posted the following in the News group / Forum on Feb 12th 2018 :-

to be translated is ...... Byte[] data = new Byte[256];

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

' ? ' - the use of a question mark to make a Type 'Nullable'

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

*** CARE *** .. Only do this with value Types - String is a reference Type !

'var'

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

Self.dg

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

Lambda Expressions :

In recent weeks we have seen a convergence between C# and X# on the syntax styles allowed

by the X# compiler.

We can now use the ' => ' operator as seen below in some working code :-

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

We can also still use the previously available code syntax with 'pipes' as in the following image :-

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

Personally I feel it will be less confusing to keep with the syntax similar to C#.

Note however, that C# does not have the expression within curly braces ' { } '.

PHH - April 11th. 2017


Floats - some differences to C# ...

Today I stumbled over an issue which needs discussion I feel.

It is the use of 'F' for floats in C# and the X# use of 'S' and 'D'.

Here is what I was trying to translate :-

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

And here is what I eventually got to work for me :-

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

Note that the 'F's have become 'S's.

Here is what Chris Pyrgas from the Team had to say :-

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

In X# syntax, we went for post-fixes that we thought make more sense:

- System.Single (REAL4): "s"

- System.Double (REAL8): "d"

So a single precision literal can be typed as 1.23s and a double precision one as 1.23d.

You can argue we should use the (IMO confusing) same post-fixes that c# uses for consistency with it, but

this would further complicate things, because what we know as "FLOAT" at least in VO (a double precision

type with formatting information embedded into it) is something very different to what c# means

with "float", so this would be a source for a lot of confusion.

Btw, in x# (and in the latest vulcan builds) we have also introduced the /vo14 compiler option, which, if set,

treats literal decimal numbers without a postfix (like 1.23) as FLOATs (for VO compatibility), otherwise it

treats them as REAL8 (System.Double), which has better performance.

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

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

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

PHH - 9th. February 2018


Objects - Instantiating and Initialising

Okay guys, this is something we all do a LOT !

So we need to be good at it and know all the various ways that are available to us.

In this section we will cover making variables from Structures as well as Instantiating classes.

We will see how to 'make' what we developers call variables, often 'local' variables.

Let us start with instantiation ...........

Instantiation :

Instantiation is the process in code of creating an object from a class. Usually the class

has been defined previously.

[ .. 'anonymous' Types are a bit of a special case, but important .. ]

.NET classes are either already in existence within standard Microsoft namespaces, or they

are defined by the developer himself/ herself.

Let us take a very simple example.

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

Above we are using the List<T> definition within the System.Collections.Generic namespace.

Note the last pair of characters, the curly braces. These basically mean "make a new instance of".

Look in a previous sub-section called "Declaring and Assigning variables" to see other ways to

make the declaration.

Lets stay with some more simple data types for now, before we move on to our own business types.

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

Now lets try a few numeric data types :-

How about

Initialisation :

PHH - April 11th. & 12th. December 2017


from Appendix 'X' of previous ClickStartLINQ

We can't waste good work, time and effort, so here is what I did last year before Cologne :-

Since I started coding in X# some weeks ago, possibly a month, there have been some very

useful changes and updates to the compiler by Robert and his team.

We need to know just what these changes are and how to use them to make our coding life

easier, as well as more rewarding on a design and use point of view.

Many of the changes the Team has made can be beneficially used when programming LINQ stuff.

Robert said this to me very recently :-

It has lots of fixes and new features compared to the last public beta. Some new features that

we discussed on the forum and that may be interesting to you and that you can use in your examples

to show our progress ;) :

ℜ• Fix for the problem with the LINQ join clause

ℜ• Added Collection Initializers

ℜ• Added Object Initializers

ℜ• Anonymous type members no longer have to be named. If you select a property as an anonymous

type member then the same property name will be used for the anonymous type as well.

The attached example uses all of these (compile in Core dialect) - this is shown in full in the previous

sub-section from here.

Some other changes:

- We have added a .config file to the exe for the shared compiler that should make it faster

- Many changes in the VO/Vulcan compatibility area

Robert

*** NOTE *** The attached example uses all of these (compile in Core dialect) - Robert posted!

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

Well folks we have some nice 'extra' ways to instantiate objects and initialize collections in the recent X# compiler.

This also extends to Anonymous types, which are more than useful when it comes to LINQ and handling our data

collections in X# .NET.

First lets see a basic business class of mine :

I will keep it as simple as possible so that you can see exactly what is going on.

If this was a class that I needed to use in my business app solution then I would add extra 'bite' to make it

work with WPF data binding as well as LINQ with Entity Framework.

But for now a simple class definition will serve us well. Checkout below :-

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

We have a single Constructor which sets each property to a default initial value - not a lot of use really,

but it is a habit of mine NOT to leave properties unassigned.

We may need to change this the more we use LINQ with SQL and Entities. As NULLs are an integral

part of SQL 'life'.

Now the above class is upgraded to be usable with SQL Table data - reading in, and writing out of the application.

"Rule One for SQL work - deal properly and correctly with NULLs."

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

If you are going to use 'L2S' and/or 'L2E' then you had better get used to 'Nullable' types.

Note you can't do this for Strings as we get a compile error. A string can be NULL as we will

see later in this section.

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

The standard (basic) ways to instantiate a business object :

Let us now use this simple Class to see how we can instantiate an object in code. The easy and common way.

Check the following image to see our first test and what we get at runtime :-

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

Doing things the above way we get a new object of our class which has all the default values set to the named properties.

So printing out one like 'LastUsedDate' provides the screen display seen above.

Now then, lets keep the Class definition as it is, but assign other values of our own to the properties :-

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

Yes, this all works well and we should all be familiar with this simple approach.

But what about taking things further ? Lets start a new sub-section seen below .............

Basic - Using Input Parameters :

Let us now create in our code of the Class, a second Constructor which accepts input parameters :-

[ .. study lines 220 through 227 .. ]

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

Okay guys, we can now use the second Constructor overload to do something like what is seen in the next image :-

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

However, we need to provide each and every input parameter for the overload, as well as having them in the right order.

Study the following details :-

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

One problem I have with this is that items are 'positional', and by this I mean the meaning of the value simply depends on where

it is in the order - where it is in the input data order.

But we can improve this as seen in the next example :-

[ .. there are reasons I don't really like the following, but it works .. ]

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

I don't wish that my internal variable names for my Constructor parameters are used in this way - I wish to keep them

internal to my Class code.

After all we have property names and I would personally prefer to use those.

First lets do a further test with this current way of instantiating an object.

Lets provide the input parameters in some sort of random order, which at least gets us away from the 'positional' issue :-

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

Yes, above we get the correct values to the properties but in a non-specific order of assignment.

Currently, with the examples seen above, we are using the simple property definition types, not the Nullable ones.

Here is what we have been using for the last example / test :-

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

We could do the same with Nullable Types if we chose, but lets instead see the Class as I have defined it for use with the

LINQ to SQL examples - check below :-

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

Okay, that is fine, let now move on a pace or two .......................

The new way using 'Object Initializer' :

This is one recent thing which Robert and his Team have added into the X# compiler.

Lets first see the 'Object Initialiser' in use in working, compilable code :-

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

Notice that using our Class type 'CustomerItem2' we can define the properties to be Null if we so wish.

Note that the basic structure is " CustomerItem2{ } { } ", and yes, there is a second pair of curly braces.

Lets make a nice trial by commenting out the Constructor, and trying the same runtime code - see below :-

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

We are not using a Constructor but by the new allowable syntax providing instead property values to named specific properties.

(One or more properties.)

Here it is again with the comments hidden :-

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

So lets use this for a VERY simple test - check this out :-

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

;-0) .. for all those C# haters, there are only SIX curly braces on line 122 in our nice X# syntax ;-0))

If we have done things as I wished / planned, we should now have a new object, with one property value and the rest should be

unassigned, or in SQL terms, 'Null's.

We can extend this simple test by outputting a property we have not as yet assigned - study the following image details :-

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

Yes, folks, the empty message box means we have got a 'Null'.

But what if we wish to print out our string value assigned to the 'ProductCode' property - remember

this is not defined as 'Nullable<string>'.

This is what we get :-

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

Oh! - common stuff - handling nulls once more ;-0) We have needed to do that (handle nulls) a time or two in our coding.

The following is NOT permitted :-

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

As we can see by this error message :-

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

So we need to handle null stuff ourselves. Try checking out the follow code :-

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

The above code lets us detect the presence of a Null before we try and use it. Yes, standard strings can be 'NULL' - a good

thing to remember when doing LINQ and SQL stuff.

I don't like the non-SQL approach of .NET talking of something being equal to 'null'. Its not the NULL way of thinking, but

we are stuck with it. Something can only be equal to something else if both 'things' have a value. And NULL is not a value,

it is 'undetermined'.

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

Collection object - Instantiation and Initializing :

As you know by now, 'Collections' play a BIG part in modern .NET programming.

We need to use collections most of the time, some would say 'all of the time'.

So it is nice to have an easy syntax for instantiating and initialising them - along the lines of some of

what we have already seen above.

Lets go straight away to seeing an example of a collection of 'CustomerItem' elements. Personally, I

like to talk about a collection of items, but we may have too many 'item' words in my example 'wording' here if

I do ;-0)

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

[ We now have lots of ways of declaring our local variable, and I feel that each has its place in my coding. ]

Yes, this is pretty nice and very much in line with what Microsoft does with C# - how we construct our collection..

Its great to have this now in X# - well done Robert and the Team.

Lets display our newly made collection, using our WPF form (DataGrid) control - see below :-

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

And now we need to explore a little more how we can layout the code within the required syntax, so we can see it more clearly perhaps :-

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

Yes, I know, you will need some time and experimentation with important commas and semi-colons. Oh! and the nice curly brace, or two ;-0)

But it all is pretty straight forward and makes good sense I feel.

And it works !

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

Anonymous Types (ATs) - Instantiation :

Like them (or the idea of them) or loath them, in modern .NET coding we need to use them (ATs), at least sometimes.

So this means we need to know what they (ATs) are and how best to use them - and even possibly when to try and

avoid using them ;-0)

BUT - we can't ignore them as professional coders.

In particular, when we do LINQ coding we very often need to do our selecting of fields/columns using ATs.

To those of you who are new to Anonymous Types, the idea is really quite simple. Yes, simple.

In our code we can define and initialise a Class with no name - so in the sense that a person is anonymous if

we don't know who they are, then a 'nameless' Class is an AT.

To declare an AT we need to specify its properties, and to assign these a value. Remember, no name.

Before we talk any further / longer, lets see a working example - check out line 133 below :-

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

The variable has a name but the Class does not - the variable is an object with 'no name' - anonymous.

So in the code above we have made an object with three properties, 'Name', 'Number', and 'ATdate'.

We have also assigned values to these properties.

Note that we could have done this in any order of the properties, the result would be the same.

Lets print out some details of the object we have created in our code :-

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

Yes, the above screen display shows the values of the three properties - shown singly at the end of the text string.

Lets now go a step further and make a CLASS (AT) from properties of one or more existing objects, rather than a hard

wired name and value for each property.

Check out these details very carefully :-

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

Notice that the names of the properties shown have also been hard coded by our own hand.

Here is the isolated section of the LINQ query code :-

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

And before we go any further, lets make this more clear to the reader by adding some organised structure into our code.

Study this carefully - lines 337 to 344 :-

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

However, we can simplify this code quite a lot, provided that we are happy to have the name of the new property in the AT Class the

very same as the name in the class from which we are assigning the value.

See how much neater and clearer this is in the next image :-

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

In the above image see how the column headers in the data grid are now changed to those property names of the originating

object properties.

Probably this is the way many of us will do it, most of the time in our application solutions.

So above, our originating objects are two, one a 'Customer' object, and the other a 'CustomerItem' object.

The properties are pretty obvious to see ..........

Time to move on, see you in the next section.

PHH - 15th. & 17th. November 2016.

Addendum : - "Mix and Match" :

Oh! - I should have said that we can also mix the way we declare our properties, and how they are named.

This may seem pretty obvious to many of us - BUT - it does need saying as it is a big part of what is possible.

Lets see what I mean by looking at an example - see and study the image details below :-

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

In the code lines above from 372 through 379 we have a mixture of default property names as well as defined property names.

I have chosen 'Code', 'Tot', 'Last', and 'First'. The rest are to take the default names - that is those names as used for properties

in the originating objects where the values are stored. In our case 'c' and 'ci', which are 'Customer' class and

'CustomerItem' class objects.

Please note that a point (full stop) can't be added to the total heading, so we can't have 'Tot.' Rules of naming properties, and

characters which are not allowed.

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

PHH - 18th. November 2016.

PHH - 19th. December 2017.


Properties in Classes

For VO guys :- Properties can be used to assign and access values of a type.

The property syntax allows us to define read only ('RO') properties and to assign actions to the assignment of a property.

There are roughly 3 types of properties:-

- Auto properties, where the compiler declares the field that is to 'back' the property

- Single line declarations, with a simple GET expression and SET expression(list).

- Multi line declarations with a separate GET block and/or SET block

I will consult the X# Forum for suggestions on all variations of the syntaxes possible.

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

The availability of Properties in our own defined Classes has made coding much more enjoyable.

I won't even bother to discuss "Access and Assigns" ;-0(( Thank God they have go for good.

There are a few ways we can handle (and code) our Properties. But lets start simple, and checkout

some examples from working apps, and working code.

Here is a typical example :-

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

Note lines 725 through 729 in the above image.

Year is an 'Enum' and ExamScores a generic List. Below is the 'Enum' definition :-

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

'Auto' is a very useful syntax variation, I use it a lot in writing these eNotes.

Lets now move on and make some improvements - checkout line 727 below :-

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

Line 727 makes the property 'Fullname' read only. We can't 'assign' to it as there is no 'SET' property.

So the value of 'Fullname' is generated at the time of 'action', when it is required, or called upon in the code.

Okay then folks, lets do something a little different, lets define a private Field and use it with a related

Property - see below on lines 697, 704, and 94 the the subsequent image :-

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

Instantiating and using the the class :-

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

Arrrh! - if we forget to provide a date then we get an 'empty' message box.

Lets now put that right by forcing an assignment within the Constructor - check below :-

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

Well, at least you know when I created and run the app with the above syntax ;-0)

Note that we can specify (or define) Get on its own or indeed Set on its own, all on one line.

We can now fully investigate a full syntactic way to define a Property, in a way useful when doing

data binding with WPF and XAML.

Check this out in the following part section ............

Data Binding in WPF and tracking Property changes :

When we are 'DataBinding' and binding a Collections of objects of a similar Type to a suitable

control on a WPF form, like ListBox, ComboBox, or indeed DataGrid, we need to be able

to track the changes made to Property values within the objects.

Below is the classic way this tracking is done - remember that 'binding' is done with Public

Properties on the generic Type held in the Collection.

Study this carefully - lines 80 through 88 are the ones in particular to study :-

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

I have shown just one of the Properties expanded (above) as the others are VERY similar.

The above code syntax and structure is used by me in my WPF data binding samples apps a GREAT deal.

And it works - and works well.

Moving on ..............

The single line syntax can be very useful as well - check this out :-

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

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

Below the two 'DateTime's show the 'before' and 'after' situation for me clicking OK.

Note that I took a few seconds or more while typing some text into VS and H&M - check this :-

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

There should be enough sample code syntax above to get you well on your way to X# coding.

Here below, is a nice post from Wolfgang, an experienced developer in the xBase community.

Summary - from Wolfgang Riedmann :

Here is a useful and helpful post from Wolfgang - it helps reinforce what I have said previously :-

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

Thanks for that Wolfgang !

Time to move on to another sub-section.

See you there ...............

PHH - 12th. & 18th. December 2017.


'Tuples' - something new

Here is a useful web link :-

https://msdn.microsoft.com/en-us/library/dd414846(v=vs.110).aspx

It says this :-

- To represent a single set of data. One data record, with fields.

- To provide easy to, and manipulation of, a full data set. Many records (rows) of data.

- To return multiple values from a method without the use of out parameter in C#.

- To pass multiple values to a method through a single parameter.

Tuples being structures makes the 'light weight'.

We need to check on how to use 'out' in X# syntax.

PHH - April 11th. 2017


A working example

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

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

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

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

PHH - 19th. December 2017.


Nullable Type and Nullable Structure

Although we often hear people taking of 'Nullable Types' it would appear as though it is "Nullable Structure"

that we need to consider when defining our business Classes. And / or their members.

First lets look at what Help Viewer 2.2 say of the 'Nullable Type' ...........

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

And then this below :-

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

In fact there is not much in the eNotes, we should expect more.

So now lets look at the related topic (link in the eNotes) for 'Structure' :-

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

The above detail is starting to look much better I feel.

Now lets see some details for Properties :-

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

As well as some details for Methods :-

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

AND, the following definitely sounds as if we are on the right track with 'Nullable<T> Structure' - check the following :-

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

Okay then guys, lets see if we can put some of this into working practise - working and testable code.

Lets first of all see if the C# syntax for 'Nullable<DateTime>' as 'DateTime?' works for us - check below :-

[ .. line 803 is what we need to view .. ]

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

Well, that is good, as the ' ? ' syntax is very handy to use - quick and easy.

Let now check compilation with a small test too :-

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

And as this seems to work well, we also need test the older syntax, we call it 'standard' syntax I suppose.

Compare these two different syntaxes :-

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

And now testing the second of these :-

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

Fine ! we are on our way, as far as Nullable structures are concerned.

Look at the child topic from here to see a real Class in action.

Now that I know I can use ' ? ' I will convert and test the class shown in the child topic / folder.

See you there ...............

PHH - April 11th. and 19th. December 2017.


a working sample

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

PHH - 19th. December 2017.


Strings and the alternative use of Escaping

*** NOTE *** we can't ignore the C# ways of displaying strings as we MUST be able to read and

use C# samples.

In both C# and X# we have two different ways of specifying a string of characters.

Both make sense.

However, in one way of looking at things the C# way in the other way around to the X# and xBase way.

I prefer the xBase way of doing "things strings" but I need to be able to read C# code as well !!!

We have a prefix to the sting in X# to make it an escape sequence, but have no prefix in C# for the same strings.

In C# we add a prefix to remove the escape sequence capability of the string.

So in X# we have e"..." meaning the same as C# syntax of "..."

and then in X# we have "..." meaning the same as C# syntax of @"..."

So reading C# we can express things another way :-

"..." >> e"..."

and

@"..." >> "..."

Oh dear ! - I hope this doesn't read as messy as it feels when I write it !?

However, I can say that with good and accurate (correct) examples to copy and adapt, it is NOT half

as difficult as it looks and sounds.

I suggest you spend a little bit of your time trying out some code and X# syntax for yourself.

Good Luck.

PHH - 24th. October & 28th. December 2017

Addendum :

Before we get deeper into the 'ins and outs' of what Strings can do for us, we had better study the following :-

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

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

Study the above and get your head around it and the consequences thereof.

Since there is much accessible information on Strings in the Help Viewer 2.2 eNotes we can leave

this topic as far as we have reached above.

You at least have plenty of X# syntax to copy and adapt, so go and practise a while ;-0)

PHH - 28th. December 2017


C# strings ...

Okay guys, lets keep it as simple as possible - all we need is to know how to read 'String' stuff

in .NET C# code.

We need to be able to convert C# sting stuff into X# code and correct syntax.

Lets start very simple - see below :-

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

*** Remember *** the semi colon is just a line terminator character.

But then we can have this variation, using the 'at' sign - its called an 'ampersat' I believe !?

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

The '@' is included so that we can handle special characters. Or should I say we don't need

to include an extra 'back slash' in the following example :-

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

However, we can now do the following - to get a double quote ( " ) in the string we have to make

it a 'pair' of double quotes ' " " ' without the spaces!

Yes, I know, it gets a little messy to explain - so make sure you have a set of good examples to

refer back to at all times. That is what I do ;-0)

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

Here is a coded example to study - there is no point in me converting this to X# since the whole

point of the exercise is learning to read the C# coded syntax - check below :-

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

Now then guys, here is another list, or set of examples to use for future reference :-

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

From all of the above examples it seems we need to make a big distinction between when we use

the prefix of '@ and when we leave it off, or don't use it.

Oh! and be warned that strings in XAML script follow a slightly different set of rules which apply to

XML rather than code.

Lets now move on to X# where we will try and cover the same range of examples, and see how to do

them in X# syntax.

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

PHH - 27th. December 2017.


X# strings ...

Okay then folks, lets see how X# handles strings when we use the 'e' prefix for 'escape' and when

we do not use it.

It all comes down to handling special characters within the character string we are using or printing out.

We call the special cases 'escape sequences' as they are a combination of at least two characters. We will

see all of these later, in a child topic folder.

Now lets look a a couple of simple but basic examples to get us going :-

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

In the above example we are using 'e' as a prefix to our double quoted string, and this means we can use the two characters

' \" ' (enclosed here in single quotes) to represent a double quote character within the string.

Above, look at line 66 as well as the last output line in the Console window.

Of course we can use an 'e' prefix even if there are no special characters involved in the string. Try this :-

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

Now then folks, lets look at some X# code with which you may well be familiar :-

[ .. checkout line 19 below .. ]

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

We tend to use 'CRLF' as a new line in our xBase strings - I know I do, a lot ! Where the letters mean :-

r = return

n = new line

So the escape 'sequences' are ' /n ' as well as ' /r '.

There are a whole bunch of escape sequence 'character combinations' in the child topic/folder from this one (here).

Okay, so far so good, now the confusing part :-

In C# syntax the plain string "..." is the same as us in X# using e"..."

In C# we have to use a prefix of '@' in order to get a non-escape string, so @"..." is equivalent to "..." in xBase.

Okay folks, I did warn you it would get messy ;-0)

Personally I feel the xBase way is more meaningful, and easier to read.

So how about a few useful examples in X# syntax ? Here goes :-

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

The above sample syntax for X# has been copied and adapted from a Help Viewer sample.

All the above code helps produce the sample output shown in the image below :-

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

And you need to particularly check this code for creating and using a 'constant', as it is quite

different to the way C# does it for constants :-

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

I will consult the Team and the Forum to see if there are any plans to bring X# into line with C# on this

matter of constant definition etc..

Watch this space ..........

PHH - 27th. & 28th. December 2017.


Escape Sequences ...

Well guys, we need to read carefully the following Microsoft publication details :-

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

And now keep reading and learning, just a little more :-

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

Here are the sequence combinations we need to know about, even though we GUI guys may only use

a few of them :-

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

You will see some of these used in the examples we have available to us in this child topic/folder, and in the

related parent topics/folders.

PHH - 28th. December 2017.


working code

*** Below is sufficient sample code for you to copy and paste appropriately :-

#define cMessage4 "You can't get rid of me!"

FUNCTION Start() AS VOID

       Console.WriteLine("Hello World!")

      // Declare without initializing.

      local cMessage as string

       Console.WriteLine("no initialising >> "+cMessage)

      //--- Initialize to null.

      //local implied cMessage2 := null   //--- can't do this ! ---

      local cMessage2 := null as string

       Console.WriteLine("this is null >> "+cMessage2)

      // Initialize as an empty string.

      // Use the Empty constant instead of the literal "".

      local cMessage3 := System.String.Empty as string

       Console.WriteLine("and this is empty >> "+cMessage3)

      var cMessage33 := System.String.Empty

       Console.WriteLine("and this is empty also >> "+cMessage33)

      // Initialize with an escaped string literal.

      local implied oldPath := e"c:\\Program Files\\Microsoft Visual Studio 8.0"

       Console.WriteLine(oldPath)

      //Initialize with a regular string literal.

      local implied newPath := "c:\Program Files\Microsoft Visual Studio 9.0"

       Console.WriteLine(newPath)

      // Use System.String if you prefer.

      local greeting := "Hello World!" as System.String

       Console.WriteLine(greeting)

      // In local variables (i.e. within a method body)

      // you can use implicit typing.

      var cTemp := "I'm still a strongly-typed System.String!"

       Console.WriteLine(cTemp)

      // Use a const string to prevent 'message4' from

      // being used to store another string value.

      // ... look in defines at top of page ---

       Console.WriteLine(cMessage4)

      // Use the String constructor only when creating

      // a string from a char*, char[], or sbyte*. See

      // System.String documentation for details.

      local letters := <char>{ 'A', 'B', 'C' } as char[]

      local implied alphabet := letters.ToArray()

       Console.WriteLine("the count is :- "+letters.Count().ToString(),"my count :")

       Console.WriteLine("Press any key to continue...")

       Console.ReadKey()

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

return

PHH - 28th. December 2017.


String formatting ...

Strings are VERY commonly used in .NET coding, and to help the developer guys Microsoft

have provided some formatting features and facilities.

Starting with a Help Viewer 2.2 search :-

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

We find details about using the 'StringFormat' method.

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

If we look into the 'String' Class information we find this :-

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

So the STATIC method for the string class does the formatting 'tricks' for us.

All we need do is learn all the little things we need to know to make this method work for us, and our string data.

Lets have a look at some sample X# code converted from the C# example in Help Viewer :-

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

Note above that line 63 is where the formatting takes place - yes, where the fun is at !!!

Her is a tidier version of the code, the whole 'Function', before we adapt it :-

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

Now then folks, we can omit the creation of the 'output' local variable and add some formatting directly into

the 'WriteLine' method as a parameter - check this out :-

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

Line 66 shown above does not have any special characters requiring the 'e' - escape - but it does not hurt to have it there.

If we add one or more special characters then we must have the 'e' as seen below :-

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

In the image seen above note the temperature output in the Console.

In another and separate example study another use of 'e' with formatting.

Note below the use of 'e' at the start of the string - see earlier for information on the 'escaped string' approach :-

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

Okay folks, time to move ahead, lets move on ..........

Lets now see some help notes about formatting :-

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

Although we may, or may not, use string formatting a lot, we do need to know about it, as

well as having a good working 'feel' for it, and what it can do for us.

Now go and do some trials and tests for yourself.

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

PHH - 27th. December 2017.


working code

USING System

USING System.Collections.Generic

USING System.Linq

using System.Text

begin namespace ConSamplesDelegateXS

   FUNCTION Start() AS VOID

       Console.Clear()

       Console.WriteLine("lets test a Help Viewer sample ...")

      var dat := DateTime{2012, 1, 17, 9, 30, 0}

      var city := "Chicago"

      var temp := -16

      var output := String.Format("At {0} in {1}, the temperature was {2} degrees.",;

                                     dat, city, temp)

       Console.WriteLine(output)

      // The example displays the following output:

      //    At 1/17/2012 9:30:00 AM in Chicago, the temperature was -16 degrees.

       Console.ReadKey()

  return

END NAMESPACE

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

.................

       Console.WriteLine(e"At {0} in {1}, the temperature was {2} degrees.",;

                                     dat, city, temp)

      // The example displays the following output:

      //    At 1/17/2012 9:30:00 AM in Chicago, the temperature was -16 degrees.

       Console.ReadKey()

  return

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

PHH - 27th. December 2017.


String 'Converting' ...

There are ways to convert a string of digital characters into a numeric type (a number).

In fact we can convert such a string into a few different (various) types.

We also in .NET have ways to test if the converting is likely to work, or be okay - or not.

Lets look at some code from a working app - checkout below at line 1031 :-

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

Obviously, on line 1031 we are trying to convert our text string into a numeric 'double' type.

If we are to write robust code then we do not wish to wait till runtime to see if the conversion will work!

We need a way to test if the conversion will be okay. The following code is part of a method :-

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

In the above code snippet it is not clear as to what else is going on within the method - BUT - you

should get the idea of testing and using 'TryParse' to make our code safe for all eventualities at runtime.

Here is a C# snippet to handle any error if we try to Parse without first checking / testing :-

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

So we seem to be using the 'Convert' class - lets look in Help Viewer for some more information :-

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

And now the class itself after we find it :-

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

We will obviously have rather a lot of STATIC methods within the class - read this thoroughly :-

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

So from these MANY static methods lets look at the one we have been using above in our test code :-

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

And, as we may expect, this class with its many STATIC conversion methods have been around in the

Framework from very earliest times - check this information :-

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

I will now leave it to you to try out some of these methods.

As well as the convert methods try out the Parse and TryParse methods of the numeric Type in focus.

PHH - 14th. March 2018.


String 'Constants' ...

Although we can define 'Constants' for many different Types, here we will look at just String constants.

A constant is just what is says - constant, and therefore can't be changed once it is created.

Usually we use a Field in our class, and declare it appropriately

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

In the above class definition we have declared a Field called 'cMessage13' and assigned it appropriately.

We may have done this another way by using a #define as shown below :-

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

It would appear to me that both of these ways have their merit, but for simple and general use lets

stick with the 'Field' approach, and keeping our code all together as .NET type of code and syntax.

Once we have a constant field declared, we use it as shown below on line 42 :-

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

Note that on line 42 we do not use an instance name, but the class type.

The class type is followed by the Field name.

Oh! we had better show that again, but this time with some screen output - check this out :-

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

The access to the constant defined as a Field above, is anywhere we can access the Class

that holds the constant Field.

So it would seem that access is on a similar basis as a STATIC member. As long as we get

the namespace and assembly suitable available, then we can access the details supplied as

constant and STATIC.

Using a #define instead :

Another way to create a constant type is to use a #define statement at the head of our code file (PRG).

This way the constant is only available to the code in the PRG file - unless we do something about it.

If we are to try and be as .NET as possible in our coding and style then we ought to steer clear of this

#define approach.

Check this out however, as it does work - see the results at the bottom of the image :-

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

Okay folks, time to move on, now that we know more about "Strings and things" !

PHH - 28th. December 2017.


XAML and escaping ...

As we can see below, it looks as though we don't have issues with representing file paths in XML / XAML script.

See lines 2 and 3 in the following image :-

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

Here is a useful link :-

https://docs.microsoft.com/en-us/dotnet/framework/wpf/advanced/how-to-use-special-characters-in-xaml

And which part of it shows this information :-

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

In the coded detail for the above characters we see the worded description is included in a shorthand form.

This is obvious when we look at the script detail in the following image :-

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

Okay I guess, but I would like to distinguish between a 'quote' and a 'double quote' if I had my choice / way.

We will just have to live with &quot; whether we like it or not.

Now then lets do our own experiments in the XAML editor - check below :-

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

So what script is it, in the XAML text editor, which shows up immediately in the graphical design surface ?

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

Oh! we have yet another issue - how do we use the keyboard to make some of those symbols in lines 36 through 39 ?

And here is a second form output scripted in a different way :-

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

Look at lines 49 and 59 to see the different, or alternative way of scripting these characters :-

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

Well, we seem to be doing very well indeed.

So how do we get 'hold' of these extra special characters ?

Here is something we can type at just about any keyboard :-

€ is made by pressing keys "Ctrl+Alt+4" where '4' is the top row key for 4. I did not have

a numeric keypad on my PC when I did this.

Moving on ..................

PHH - 29th. December 2017.


XML escape characters ...

Okay then guys, be prepared to learn more good stuff about the 'simple' string (or characters).

From the following image of the head (top) of a standard XAML file it would appear that using double quotes

does not present us with an issue as far as the slashes with a Windows file path is concerned.

Check this out below :-

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

However, there is quite a lot to know about the XML way of representing strings and many special characters.

Since XAML is effectively XML with knobs, bells, and whistles, it still obeys the XML rules etc. for special characters.

Here is a good place to start learning :-

http://www.w3.org/International/questions/qa-escapes

And here are a couple of useful snippets from this source :-

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

And yet another one, the second snippet :-

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

Okay then folks, over to you if you need more detail or information.

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

PHH - 29th. December 2017.


Naming special keyboard characters

& = ampersand

* = asterisk

' = apostrophe

Ampersand, asterisk, apostrophe ... but what about the 'at' sign, ' @ ' ?

Let study some personal comments as well as some Wiki 'history'.

First the personal comments :-

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

And now for a bit of documented history :-

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

So I am sorry the idea that it may be 'ampersat' is a great one BUT wholly unfounded (not true).

Time to move on ................

PHH - 29th. December 2017.


Accessing from the keyboard all the ASC characters ...

Okay then, we all need to know this next 'stuff' :-

http://www.asciitable.com/

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

As well as the extended codes :-

[ .. some of those below we use in the Welsh language here in Newport, South Wales .. ]

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

Oh! and now the bit which may cause some of us some grief - read this :-

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

Yes, with my short (cut down) keyboard, which I like and have many, I can't get key press

access to the 'ALT' characters mentioned above. Seem to have tried most combination / possibilities.

So I got out my old long keyboard and used the numeric keypad with the ALT key to generate

the following characters :-

° Ï } ~

The first character above is that of '0176' in the above image from the web search I did.

So then, I am therefore not sure where this leaves us when we use Tablets and Surface Pro

type machines.

It looks like many laptops will be okay, especially if they are 'big and clunky' like mine, with a

keyboard which used to be 'standard'.

Time to move on, you can do the extra research if you wish to go further with this stuff .....

PHH - 29th. December 2017.


Anonymous Methods

Okay guys, later in the eNotes we will see a lot about Anonymous Types (ATs) when we are

dealing with Collections, LINQ, and Lambda Expressions.

So we need to find out what Anonymous Methods are, even if we never get to using them ourselves.

I have to confess that I have done a lot with ATs but little to nothing with AMs (anonymous methods).

So lets find out and learn together ..............

Read the following two images for meaning and detail, then we can have a brief discussion :-

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

And also the following, this has more meaning :-

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

Okay then, it appears that we need to be deeling with Delegates before we are likely to need to call

on Anonymous Methods.

This could be a bit to far for the average guy getting into using X#.

So for now, I will continue to fill-out the other more standard parts of these eNotes, and

return to AMs later when I return from Cologne 2018, at the end of April.

Keep on learning - see you in the next subsection .................

PHH - 12th. & 21st. & 28th. December 2017.


Random numbers

Random numbers and their generation is not a big topic, but quite a relevant one for many of us developer guys.

Its one of those topics that we don't often reach for, but when we do need it we need to be able to use it in the

correct fashion - effectively and wisely, and understand how random numbers work in .NET

We need to know how to get the best out of what classes and members are available to us.

Lets start with the eNotes from Microsoft - in their "Help Viewer 2.2" tool.

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

Okay, so we have the class available to us in the System namespace.

What about Constructors to allow us to instantiate ? Check below :-

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

At certain times we will need to use both of the above - seeding is generally done during development and testing phases.

Lets now have a quick look at the Methods available to us in this class - see the following details :-

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

For most of the use I have for Random numbers, I only need a few of the above methods.

However, whichever you think you need I suggest you test thoroughly to see that it does what you expect it to.

In the child section from here I will use 'Next(Int32)' to do a useful task.

Before we rush off and do some coding, lets read a small amount of background information :-

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

I have not copied the example as it may be best to go now to the child section and see my test sample.

See you there ...........

PHH - 22nd. & 23rd. November 2017.


Sample use of Random

For my 3D example provided in the subsection on Arrays, I need to provide random numbers of the

numeric set :- -3, -2, -1, 0, +1, +2, and +3

This can be done in the following manner :-

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

The above code tests out okay, and does not apparently give -4 nor +4 in my simple testing process.

However, if we use another overload of the Next() method we can simplify things even further - check below :-

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

I kind o' like this above approach - BUT - we must remember that the first number is the lower limit and equal

to or greater than (that is inclusive), where the second numeric input is leas than (or exclusive).

So in my above sample code we may get -3 but will never get +4. Seems odd, but then that's the designers

behind .NET - must be C++ guys I feel ;-0)

So now we can run this second (above) test method during our initialization method for the project :-

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

And since the test code was for me to experience runtime values, we will show just one sample output :-

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

This is enough for me on 'Random' at the moment, as I will return to the '3D array' example to finish development there.

I suggest you go and have a look at the 3D array sample.

See you again soon ..............

PHH - 23rd. November 2017.


Control Structures

Well guys, since much of the test code and experimental running of code in this section

is not directly to do with Collections, I can use a Console app for testing - makes a rare change

for me ;-0) Usually I prefer WPF projects so that I can bind the collections to form controls

like ListBox and DataGrid.

So lets start a new project and make it a Console one - see below :-

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

Well, the small amount of detail was all created / done by the template, so no glory for me there ;-0)

Lets use this to explore some of the sub-sections in more detail ..............

See you there.

PHH - 16th. November & 22nd December 2017.

Addendum :

"One way in, and one way out."

I was taught to follow the above advice for all code sections like Loops, Functions, and Methods.

Do NOT add extra lines of code to 'jump out' of such code structures.

Let the natural 'control flow' sort things out as it was intended.

Other guys should also be adhering to the adage of "One way in, and one way out."

PHH - 30th. December 2017.


NOP ... and what it does

Before we get to the sub-sections in which we study the code structures to control 'flow' in our

program logical sequencing, we need to know about the 'NOP' keyword or statement.

Here is what Robert and his TEAM has to say :-

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

So it would seem that we use 'NOP' in place of a blank line (or two), simply so that the compiler

then knows that we intended to place no code there - or leave it blank intentionally.

We will see NOP in use with "IF / ELSE / ENDIF" and "Do Case" as well as "Switch".

Before we move on lets see out developing Console app using 'NOP' :-

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

Line 18 above is the focus for us at the moment.

Moving on .......... See you in the next subsection.

PHH - 22nd. December 2017.


If .. Else .. Endif ....

A personal note on my views of, and coding use of, IF/ ELSE/ ENDIF :-

This logical switch type of control code is the least of my favourite parts of programming.

Over the years I have seen so many coding mistakes involved with logical code, and IF statements in particular.

Guys rush-off 'IF' statement code without fully understanding logic - and/or fully testing it.

And for those who nest IF statements, you must be mad and/or stupid !!!

Use IF statements sparingly, and when you douse them, keep them as simple as possible.

Then you will sleep peacefully in your bed.

So having warned you, lets see how to code the X# syntax and get some useful results.

Alwyn Jennings-Bramly has this to say in his book on Vulcan.NET :-

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

Followed by this :-

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

Yuck! - this is all getting a bit confusing I feel.

Lets show at least a simple X# code example in my console app - check below :-

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

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

PHH - 22nd. December 2017.


and also IIF ...

Also from Alwyn's eBook :-

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

Now it is time for something (a contribution) from Robert and the TEAM :-

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

Since we need to read and understand C# syntax and code, we had better read the following as well :-

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

Before moving on to another topic, remember to try and always simplify the logical 'flow' of your code.

Don't write spaghetti code :-0))

PHH - 22nd. December 2017.


Do ... While

The 'DO WHILE' statement is used to execute a loop of code as long as a condition is TRUE.

This control structure causes a repeated 'action' to take place in code, as long as the condition

following the WHILE is 'True'.

The code shown below will continue to be executed as long as the 'MoveNext()' is True :-

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

So the 'loop' code is no longer executed when the condition changes.

The 'Do While' structure is often compared with the Repeat / Until control structure simply because

one ends at the top of the structure and the other at the end of it.

Checkout the next sub-section where we will look into Repeat / Until.

*** NOTE *** an endless LOOP can be created using a 'Do While' as seen below :-

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

Sorry ! - for 'goon' read 'go on' !!

Now then here is an old fashioned way to use this structure - not nice, but it works :-

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

Now lets code the control structure so 'it' does the work for us :-

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

That looks and reads better.

Never try and take control of a 'loop' structure - all of then can do the work for us - if we let them ;-0)

Finally, here is a nice old fashioned style piece of code - brings back memories :-

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

Yep! - have coded stuff like that above a few zillions of times in the past.

Lets move on to the next sub-section ..................

PHH - 16th. November & 21st. December 2017.


Repeat .... Until

The 'REPEAT UNTIL' statement is used to execute a 'block' / 'loop' of code until a condition is FALSE.

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

Use EXIT to terminate a 'REPEAT UNTIL' structure based on another condition - one other than the 'action'

,'looping', or 'until' condition.

LOOP, by contrast, jumps back to the first statement inside the 'REPEAT UNTIL' statement.

LOOP causes the 'until' condition to be by-passed, or ignored.

Here is my simple first attempt :-

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

Okay, the above code is easy enough to follow.

Lets now try to include a 'LOOP' back the the 'REPEAT' statement :-

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

The above code misses out adding a difference of 13.

So lets improve things so we can provide a parameter to start the difference value.

Here is the calling code and the resultant display :-

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

So what does our method code now look like ? Check the following details to see that

we also have an 'Exit' statement as well as a 'Loop' :-

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

And now lets add a bit more 'spice' and have a bit of 'fun' :-

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

What value did we provide as an input parameter ? Check below :-

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

Notice that in the message box display we are still missing a difference of 13.

Time to move on ............

PHH - 16th. November & 21st. December 2017.


For .... Next

This control structure is showing its age, I first used it back in 1969 !

Yes, its a basic control structure which can be used to do most things - BUT -

these days there are better ways to do things, and it is an 'indicator' as to whether you

are coding things right, and in the modern style.

Rule '99' : "If you hardly ever use 'For / Next' loops then you are doing a lot of things right."

However, just because we may not use something often, does NOT say that we don't need

to know how to code it with the correct syntax.

So lets explore ways of coding 'For/Next'.

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

This is probably my preferred syntax, however there are alternatives.

Lets check this out :-

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

The above looks a little bit old fashion to me. The first part of line 173 that is.

However, we can make a third attempt at this with a more modern look to it :-

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

I think I prefer the 'for var' syntax to the 'for local implied' - now here is a fuller code listing :-

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

Okay guys, enough for now, time to move on ..........

PHH - 16th. November & 18th. December 2017.


Foreach ....

The 'foreach' control structure tends to be commonly associated with iterating through Collections,

and often as a result of a LINQ query.

Here is such an example - first we see the query (lines 525 through 528, and then the iteration :-

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

Lines 531 to 533 find all the names in the collection and concatenate a results string.

Here is the result :-

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

PHH - 16th. November & 18th. December 2017.


For items in a Collection ....

Increasingly I use this structure in my coding.

On the other hand I rarely now use For / Next.

Foreach does go a little hand in hand with this LINQ query approach.

Check this out :-

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

PHH - 18th. December 2017.


Do Case ....

Below is what Alwyn Jennings-Bramly has to say in his eBook on Vulcan.NET :-

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

The problem with 'Do Case' is that we often see simple examples, showing the 'basic' operation - BUT - not

those which illustrate, or highlight, what can go wrong if we don't code our samples / examples, correctly,

or at least with knowledge and understanding.

We should all know by now that when it comes to LOGIC we can easily get stuff 'wrong' !

We need to be careful.

In a little while I will get into creating some meaningful X# examples, and testing them.

For now lets see what another Forum guy (Frank) has to say ..........

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

Below is from Frank :-

SWITCH is checking against constant, DO CASE checks anything.

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

Local test as STRING

Local age as int

local Name as STRING

... // something stored in test

SWITCH Test

CASE "a" // "a" is a constant

...

CASE "b"

...

OTHERWISE // I have always OTHERWISE, so it's clear there is a hidden path

NOP // In this example nothing to do

END SWITCH

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

DO CASE

CASE age < 50

...

CASE age > 60

...

CASE Name == "Phil"

...

OTHERWISE

NOP

ENDCASE

I've read that C# will now allow other than constant in SWITCH cases.

Don't know if X# follow this.

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

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

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

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

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

PHH - 16th. November & 24th. December 2017.


Loop ....

The 'LOOP' statement jumps back to the beginning of the current 'DO WHILE', 'FOR NEXT', 'FOREACH NEXT'

or 'REPEAT UNTIL' block.

Basically, when we use 'LOOP' we are missing out the execution of the rest of the code within the 'block'.

If we study the following image we see that if we get to execute line 80 we do then NOT visit lines 82,

83, and 84.

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

Once again I would advise a note of caution with the use / abuse of 'LOOP'.

If we design our For /Next and other looping code correctly we should let the body of the code execute

fully - a poor use of LOOP is like turning our code into 'GOTO' spaghetti of the early Basic language style.

Lets move on to see SWITCH from the .NET world ............

PHH - 21st. December 2017.


Switch ....

Switch is new to use guys of the xBase community.

It was first available to us in .NET through C#.

To see what the TEAM and Robert had to say see the child sub-section from here.

Below is a working example from some exploratory research I am doing for Cologne :-

Here is the 'Enum' I will use in the Switch control structure :-

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

Note above that in the above image code we could just as easily leave out the keyword 'member', as

we see in the next working code image :-

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

I suspect that most, or all, of us will not bother with the word 'member' when we code our apps.

Now then, lets see a simple way we can relate a 'Mask' enum option with a RegEx option.

First lets see a display of the Regex options available to us :-

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

And now the Switch statement being currently used to set the Regex option from that already

set for the Mask enum option.

Check this out carefully :-

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

Okay, I hear you say, not a VERY clever example - BUT - it does what is required, and it does

compile.

What are its limitations ?

What are the better ways to use a Switch statement ?

I am sure we can find many more examples which seem more obvious, or better !

However, for now at least the syntax is available for us to copy, amend, and use.

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

PHH - 18th. & 20th. December 2017.


From the X# help notes

Well, guys, it looks as though what I read in the X# eNotes from the TEAM is not yet available to us.

It would seem that C# 7 is what will give us SWITCH behaviour similar to our current 'Do Case' structure

of the xBase part of X#.

Till we have this functionality we need to know when to use Switch and when 'Do Case'.

We need to know the best way to use both, and their strengths and limitations.

So looks like I need to do some work ;-0) Shame, I was hoping to avoid this outcome.

Lets roll up our sleeves and get cracking ...................

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

Here is what Robert and his TEAM published - early days :-

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

So it looks as though for new code we write it should be based on 'Swithch' and not 'Do Case'.

Check this out too :-

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

PHH - 20th. December 2017.


My experimentation

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

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

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

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

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

PHH - 24th. December 2017.


Comparing 'Switch' with 'Do / Case' .......

Switch is not a replacement for Do Case - at the end of December 2017.

Do Case is currently more flexible in what can be coded into the Case statements within the multiple switch.

Here I use the term 'multiple switch' for what the Do Case is and not because the .NET language feature

is called 'SWITCH'.

At the time of writing this subsection C# 7 is promised and as part of the new functionality we are

going to get something more in-line with what Do Case can provide.

This probable change in SWITCH may not be 'ALL' good !

By this I mean the current SWITCH functionality is in many ways simple and robust, and is easily and

logically understood by most developer guys.

The current 'Do Case', if used unwisely, can cause logical headaches - meaning it is possible to write

code which apparently works, but which has hidden logical inaccuracies built into it by error.

With all these control code structures it is far wisest to keep things as clean, clear, and simple as possible.

A little bit of extra code typing in the control structure itself, is a GOOD pay-off for creating robust code, free

from logical errors.

So my advice would be to use the existing simpler 'SWITCH' statement whenever possible.

Maybe there is a clever way to use IF statements with Enums and a SWITCH statement - one which is clear

to read and interpret, so removing logical errors (or logical misunderstandings).

We need to do some experiments and testing I feel ;-0)

PHH - 22nd. December 2017.


Navigation: »No topics above this level«

Return to chapter overview

Objects - Namespaces, Classes and Members

PHH - April 11th. 2017


Navigation: Objects - Namespaces, Classes and Members >

Return to chapter overview

Namespaces

PHH - 13th. December 2017.


Navigation: Objects - Namespaces, Classes and Members >

Return to chapter overview

Properties

PHH - 13th. December 2017.


Navigation: Objects - Namespaces, Classes and Members > Properties >

Return to chapter overview

in existing .NET Classes

PHH - 13th. December 2017.


Navigation: Objects - Namespaces, Classes and Members > Properties >

Return to chapter overview

in our own business Classes

PHH - 13th. December 2017.


Navigation: Objects - Namespaces, Classes and Members >

Return to chapter overview

Methods

PHH - 13th. December 2017.


Navigation: Objects - Namespaces, Classes and Members > Methods >

Return to chapter overview

Void

PHH - 13th. December 2017.


Navigation: Objects - Namespaces, Classes and Members > Methods >

Return to chapter overview

Returning data

PHH - 13th. December 2017.


Navigation: Objects - Namespaces, Classes and Members >

Return to chapter overview

'Static' - Methods, Classes, and Properties

PHH - April 11th. 2017


Navigation: Objects - Namespaces, Classes and Members >

Return to chapter overview

When to use 'Static' members

PHH - April 11th. 2017


'Tuples' - more than one item 'out', as one item !?

What a strange sub-title for this section ! :

We have all been taught that Methods of a class can have numerous, and varied, input parameters,

BUT, that they must always return just one 'thing'.

To be able to return just one item of either a simple data type, or a complex business class, is a bit

of a restriction at certain times in code writing.

Lets start simply, by finding out how to say, or pronounce, the word itself :-

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

Now lets move on a step or two to read a Remark :-

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

So for example, if we wish to pass three pieces of data out from a Method, we could place these

data items in a 'triple', or Tuple with three components.

*** CARE *** this could encourage some very old fashioned style of coding.

By this I mean NOT really object oriented. We are going back to positional meaning for data items,

and not the strongly type object and named property approach. We could possibly introduce errors that the compiler

will not be able to trap / catch.

PHH - 30th. November 2017.


Objects - Casts, Type and TypeOf operator

This topic is really quite important, but it can get a little tricky to get syntax details right.

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

PHH - 18th. November 2017.


Type class

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

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

PHH - 18th. November 2017.


Navigation: Objects - Casts, Type and TypeOf operator >

Return to chapter overview

Type conversions

PHH - 18th. November 2017.


a String to a number

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

PHH - 18th. November 2017.


typeof Operator

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

PHH - 18th. November 2017.


Object.GetType method

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

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

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

PHH - 18th. November 2017.


Object.ReferenceEquals

Comparing 'Types' of object at runtime :

Sometimes it is useful to a coder / developer to be able to compare two objects at runtime to see if they

are of the same type - we may casually use the term 'similar'.

So lets see some X# code to see how this may be done.

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

*** CARE ***

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

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

PHH - 18th. November 2017.


Navigation: Objects - Casts, Type and TypeOf operator >

Return to chapter overview

Types in Arrays

PHH - 18th. November 2017.


Navigation: Objects - Casts, Type and TypeOf operator >

Return to chapter overview

Casting - changing object Type

PHH - 18th. November 2017.


Generics and Type use

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

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

When you create an instance of a generic class, you specify the actual types to substitute for the type parameters.

This establishes a new generic class, referred to as a constructed generic class, with your chosen types substituted

everywhere that the type parameters appear.

The result is a type-safe class that is tailored to your choice of types,

PHH - 18th. November 2017.


Simple example - one

Okay then folks, lets use this subsection as a way of creating and testing our very

first simple Generic class.

We need to gradually get used to the syntax of Generics.

In the following code 'T' stands for Type. Study the following carefully :-

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

Now then, having seen the coded syntax for the Class, lets now see how to use it for real.

Checkout these details - lines 40 through 42 :-

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

Well, a good start I feel - it works ;-0)

How about a couple more test - see below :-

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

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

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

So what now about a simple class object like 'Car' as our input ?

PHH - 18th. November 2017.


'Reflection' - understanding and using

For a lot of guys coming to the world of .NET from that of VO (Visual Objects) the idea of what

Reflection is, and when we as developers may use it, needs some explanation and discussion.

I have to confess that in all my ten years or so ding .NET application development, I have only

used it a few times - BUT - when you need it there seems to be zero to very little alternative.

So with this in mind we had better get our teeth into Reflection, and make it work for us, even

if it is only in a few basic ways.

** NOTE *** Before we get our sleeves rolled up be aware that the section on 'Enums', ten after

this one in the Contents list, uses Reflection to great effect to find and list etc., 'Enums' to be

found within the .NET Framework namespaces.

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

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

PHH - 14th. December 2017.


Possible uses - as seen by Microsoft

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

PHH - 14th. December 2017.


Assemblies ....

Before we start using Assemblies with Reflection related code, we need to do some learning,

and also a little bit of research on assemblies.

Lets start with .................

What is an Assembly ? :

We can search in Help Viewer 2.2 ................

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

And this then offers more information :-

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

So we can see that the 'Assembly class' is within the namespace entitled "System.Reflection".

And that the actual assembly is 'mscorlib'.

DON'T get confused about assembly within and assembly - these are just names, one is a Class and the other

a real Assembly.

Moving on ......

Where can Assemblies be found on a desktop PC ? :

Okay guys, assemblies are just files when it comes down to it.

And as we know, files must be stored somewhere in the PC system.

Most that we will deal with are '.dll' files - or '.DLL' - although the lower case is usually used.

lets look at where most of mine are for the type of Solutions and Projects I create and use :-

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

If I now put them in order of 'Type' then all the '.dll's come together - here is the shocking number within the folder :-

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

AND, of course there are other folders as well.

Now then, most often us developer guys see our assemblies in the References

section of our solution explorer pane (SE) :-

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

Any, and every, Namespace we need to use in our coding MUST be within one of the referenced

Assemblies.

It is the Namespaces which appear at the head (top) of our .PRG files - see below for an example :-

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

A point to realise / notice is that not all of the above 'using' statements are necessary for the PRG.

Some were placed there by the original template, the lower ones by me as I found a need for them to be present.

It would be VERY handy to has the C# feature which greys out and even removes unused, or unreferenced,

namespaces.

However, I can live with things as they are - so we have a few too many 'using's - so what !

Okay then folks, lets continue with our quest to find the location of the various '.dll's.

We need to look inside the Solution folder/sub-folder structure in my app - here we should

find Third Party related assemblies.

Check this out for such Third Party entries / files :-

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

Yes, as you can see, 'DevExpress.' is clearly the first part of the file names shown above.

So now that we know where the assemblies are, how to we effectively access an assembly ?

How can we 'get hold' of one or more Assemblies ? :

Lets try out some coding and see just what can be done. There are various ways of doing this.

Let us instantiate an object of a standard Type and then ass the Reflection 'system' in which assembly

the Type is to be found.

Check out the following carefully :-

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

Now we see above that we are asking for the Executing Assembly - and this returns the value / name of

"WpfCreateQRcodeXS" which is the assembly of my Project.

So now we need to change the requested Assembly so that we have returned that Assembly in which

the Int32 class resides - try this out :-

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

So above we get returned "mscorlib" - which is where we will find 'Int32'.

This is what I expected to see.

But what happens if we now change to using a Type of 'System.String' ?

Lets try and see what we get :-

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

Oh! - that is interesting !

It appears as if it is in the same 'mscorlib' namespace - which on one hand is not surprising,

however, we may have been forgiven if we had seen the 'System' assembly reference and thought

therefore that it would be within that.

Check below :-

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

When we get a spare minute we ought to look inside 'mscorlib' to see all that it contains.

For now, lets move on ...............

What is we instantiate a 'Color' object ? Which Assembly will we see returned then ?

Check this out below :-

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

Okay then folks, 'System.Drawing' is what I had hoped for, and expected.

Lets try another property of 'Assembly' before we move on ............

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

And another property of 'Current' ...............

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

Finally, we do need to know that Assemblies can also be EXE files.

In particular we have EXE files made at compile time from our Projects - here is the one I am current using

to make my eNotes :-

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

So within the Assembly we find nine (9) Types.

If we now move on, we will see in a short while what these nine Types are.

Lets see how to use an Assembly ...............

How do we use an Assembly once we 'have it' ? :

Well then guys, lets take this a step at a time.

Let us load an Assembly directly from a file on disc.

In the image below look at lines 245 through 248 :-

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

Wow! something has happened - we appear to have 322 different Types from the one assembly - named 'CSharp.dll'.

So what if we load a EXE file instead - see the results in the following image :-

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

Well, the above approach works okay - BUT - we do need to improve the layout of the results.

Here is a better attempt - checkout the details :-

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

Take it from me that I recognise some of my own defined Classes, like 'Person' and 'Order'.

'PersonGender' is a Enum of my making.

Lets see what we can learn about these nine Types when we display them in a DataGrid - here is a first display :-

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

Well, the BaseType values displayed confirm what I said about my own coded Classes and Enum.

Here are a few other (extra) columns in the same DataGrid :-

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

Lets now move on a few steps .....

Doing some Interesting things with Assemblies and Namespaces etc. :

Okay then guys, lets find out just how many Namespaces there are represented within the 'mscorlib' assembly.

*** NOTE *** The Namespace 'name' is just a property of each Type stored within an Assembly.

So the Assembly is a huge bunch of Types (classes, Enums, etc., ...) and for each Type the Namespace

property this tells us which Namespace the Type belongs to.

*** WRONG *** It is incorrect (or wrong) to think of Assemblies having sub-folders of Namespaces, and then

Namespaces having Types - this tree structure is NOT correct. Have a good think about this.

If we see a DataGrid display of all the Type entries in this assembly - part of the results display looks like

that shown below :-

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

Notice the multiple entries under the column heading of 'Namespace'.

This looks like a good case for using a LINQ statement - yes, don't panic it will be very helpful to us.

We need to find only single (or distinct) occurrences of entries under the Namespace column heading.

Here is a couple of code lines which helps us out a bunch :-

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

We needed to include the test for 'null' simply because without it I got an error. Obviously, at least one

entry under Namespace was left as 'null'.

Now we can bind the resultant list to a combo box control :-

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

And when we compile and run we see the following details :-

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

Yes, I know, there are also a lot in between the top and the bottom of the list ;-0)

How many ? Well see for yourself below :-

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

Now PLEASE do remember that this is only for the assembly called 'mscorlib' in the main .NET framework.

We now have the basis for a coded solution to have a WPF window form which allows the user to select from

a list of common .NET assemblies and then for the selected assembly show the Namespaces available.

There are more interesting jobs / tasks we can perform along the lines of exploration - and finding

the list of Class types and Enums and lots more besides.

Time to move on, see you in the next sub-section ..........

PHH - 25th. & 26th. February 2018.


Finding the 'containing' Assembly from the Namespace ...

We should realise by now that an Assembly has a large bunch of Types within it.

Many of these Types are Classes, and quite a few Enums as well.

One of the Properties of the TypeInfo items in the Collection made from 'GetTypes()' on the Assembly,

is the Namespace property.

So typically we have lots of Types in the Collection and these form 'many' Namespaces.

For example I believe 'mscorlib' has 66 different Namespaces.

What we need to know (quite often) as developers, is which Assembly contains a specified Namespace.

When we type 'USING' statements at the head of our PRG code files we need such knowledge.

Lets take an example of 'System.IO' as the Namespace - which Assembly keeps all the Types related

to this Namespace ?

We need to create a STATIC method which takes the Namespace and then searches through all the

'.dll's in the .NET assembly folder, looking inside each one for the details to compare with the Namespace

to be found.

Well, that's the theory and the plan, we just now need to try and test this approach.

Below is the start of some code to do as described above :-

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

I included above a couple of print-out lines so I could see what was going on - was I getting the correct behaviour ?

The 388 Types was for 'CustomMarshalers.dll' - and below is the first Assembly in the collection :-

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

PHH - 1st. March 2018.


Namespaces ....

Okay guys, love them or hate them 'Namespaces' are a foundation stone of .NET programming.

Understanding both Assemblies and Namespaces is fundamental to good app design and creation.

If like me, and most guys coming to .NET coding I suspect, you had a bad initial experience, I suggest

you make an effort to put your 'bogeyman' behind you ;-0)

To international guys, a 'bogeyman' is a devil, or evil spirit.

When I started with .NET I was in too much of a rush to get some code working for me, and did not pay

enough attention to the need to get to grips with Assemblies and the Namespace 'approach'.

We MUST know where our Classes are stored in the Framework, and to be aware that Class names may

even be the same from one Namespace to another.

This is reasonably unlikely in my world of coding - BUT - it does exist for me too, and a recent bit of research

for Cologne 2018 has thrown up 'Color' - which exists in two different Namespaces it would seem.

Lets make a simple start and see what a Help Viewer 2.2 search throws up :-

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

The above seems straight forward enough.

Here are some specific points to take on board :-

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

Don't for one minute underestimate the need to 'organize' code projects - be they large or small.

In fact I would say that an effective .NET programmer always organises his/her projects and solutions.

Get good with using all the features and facilities in the SE pane, and structure your projects well, using

folders, sub folders, and PRG files which are not large.

Sensible use of Namespaces fits in with organising your project as discussed above.

The Visual Studio IDE has plenty of ways to allow us to be well organised (and tidy) with our coding, so use them!

*** By using Namespaces efficiently, *** we never need worry about any Class we create having a name 'clash' with any

other Class type, no matter where it would be stored.

Meaning, we could have two Classes called 'Order' in our own code, as long as they are in Namespaces with different

names.

For example these may then be typed as 'Supplies.Order' and 'Purchasing.Order'. This may not be the best example

in the world - BUT - it does help to explain what we are discussing.

Obviously, it is then up to the developer / coder / programmer to make sure these do not get mixed up in use.

If it were me I would not use 'Order' but some more meaningful name - like 'SupplyOrder' and 'PurchaseOrder' perhaps.

The 'Safe' way to specify Types :

By 'safe' I mean that code written will work at compile time, whether or not the correct 'using' statements are supplied

or not. Quite often we see examples provided on the web in this style of coding - to give them the highest possible

chance of working for readers / users, "straight out of the box".

'Safe' style coding does add a lot of extra text and verbosity - so we developer guys tend to keep clear of 'safe' style

coding. .NET coding is all about making the best use of the 'USING' statements at the head of the PRG files we create.

Lets see an example - that below is taken directly from the Help Viewer eNotes :-

[ .. look out for the 'MethodInfo' type .. ]

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

So if we are to translate this into X# syntax and code, we can add line 26 shown below :-

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

Now when we code and run the same text Class (version 2) we can leave off the qualifying 'System.Reflection'

Namespace - lines simplified are 513, 515, 520, and 522 :-

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

And just to prove that the sample still works as required here is the output :-

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

And now the calling code :-

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

From a personal point of view, I have spent the last few years in .NET trying to always have the correct / required / suitable

USING statements at the head of my PRG code file, so as to ALWAYS have the reduced typed code - removing

the need for Namespaces in the Class definitions I created.

It does make solutions much easier to read and understand done this way.

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

Theirs and Ours - Namespaces :

The .NET Framework we all use to develop (either in X# or C# or other) has many, MANY, Namespaces.

Because Microsoft created these Namespaces I call them "theirs".

When we create an application as a developer, we make at least one new Namespace, even if we think we have not.

Most of us will in fact make a few new Namespaces of our own while creating Solutions and Projects.

Our 'Class Libraries' will also have one or more of these Namespaces of our own - I call "Ours".

The templates we call up to add new items and 'things' often makes a default Namespace for us.

Here is some information directed more at us and our Namespaces than thos of the Framework :-

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

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

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

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

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

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

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

PHH - 26th. February 2018.


All Namespaces in the Framework - A-Z with Assembly ...

To us standard developer guys it is sometimes most helpful to think in terms of Namespaces.

By this I mean that we often feel we need to add the correct Reference to the References folder

(in the SE pane) simply to give us access to the relevant Namespace - so we can add it as

part of the 'USING' statement.

PHH - 1st. March 2018.


Finding all the Types in one Namespace ...

This is the 'other way around' - finding Types in an Assembly.

We are going to examine the Types found in an Assembly and bunch together all and only those

of the same Namespace.

Lets have a look at some success first - the image below shows the Types in the 'System.IO' Namespace.

Check these out :-

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

Look at the scrollbar 'thumb' to see that not all Types are shown.

In fact there are 28 in total, as the next image shows :-

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

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

PHH - 28th. February 2018.


'Type' Class (System) - background information

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

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

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

In this section and others related to reflection, you will see the use of the operator "typeof()" quite a bit.

We will use 'typeof' later (below) in this subsection when we wish to retrieve information on the 'Type' class itself.

Meanwhile read on - I am including some of the Microsoft documentation to get us going, and in the mood !

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

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

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

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

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

Okay then folks, time for some coding action I feel.

Here below is an image of me finding and counting, all of the Properties of the 'System.Drawing.Color' class :-

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

We seem to be able to copy and adapt the code to get Methods, Members, etc., etc. - BUT - not 'Operators'.

The following shows that we may be forgiven for trying 'Operators' as all the rest, Fields, Events, Interfaces etc.,

seem to work fine when used with 'typeof' - check this :-

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

So in that case lets try some simple tests / experiments using code :-

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

So 'Color' has one Fields .......

And now what of Methods ? :-

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

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

Wow! a whopping 167 of these.

Now the failed Operators experiment :-

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

And what we get as an error message when we try it out :-

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

Lets see what the MS eNotes has to say on other 'Get' methods :-

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

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

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

I think I need to now tidy my initial test code so we can get a fuller and tidier screen display.

Check this out below (for now we will stick with the same Type) :-

[ .. whoops ! - in the following image we find details for 'Type' but use a print out header of 'Color' - sorry ! .. ]

[ .. System.Type is just another Class .. ]

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

Okay then folks, whatever counts as a 'Member' we have found 232 of the total of 234 of them.

Any ideas as to what is making up the other '2' ? We need to look at the 'Get' methods in the help documentation.

Just there now had a look and to be quite honest with you I think we have gotten all the basic things in which

standard guys are interested. Certainly good enough for me.

We should be able to re-write this code section so we could use it on any chosen Type we wish to use.

Lets give it a go :- (this time we will look at 'Color' from 'System.Drawing') :-

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

Yep! that looks okay, as we are back to finding the 150 named 'Color' properties.

Now then guys, what about our own Classes ?

Can we reflect on a simple class that we have coded ? And if so what do the results mean ?

Checkout this simple Class :-

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

So now lets use the same reflection code to examine or explore this class - check below :-

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

Well now, that is interesting - there are '8' (eight) methods and yet we seemed to define none at all.

Could this be because all Classes have a few basic ones, like 'ToString' and/or 'GetType' and a couple more

besides these ?

We need another test or two I suggest. We will do that later, first lets see what we get as results if we examine

our Main WPF Window class - check below :-

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

Wow !! - at first sight this seems way over the top, until you realise that we inherited from Window, a big class

on its own, before we add our own little bit of extra functionality.

So the LARGE numbers are correct, and nothing to worry about.

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

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

PHH - 14th. December 2017.


'typeof()' operator - exploring what it is and what it does ...

Like with a lot of topics it is good to see what the Microsoft eNotes (Help Viewer 2.2) have to say.

Here is a way into this topic, for us to explore by code as we go :-

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

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

Note above that the parameter 'int' is allowed and looks as though it does not need to be 'Int32' in order to work.

Lets read on .................

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

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

So it would appear that we can use the above at run time.

We also need to take these points / ideas on board :-

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

I am trying to get a version of this working in X# syntax. So far 'no-go' !

Here is what I have done (translated) so far :-

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

But here is the next MS example from the eNotes - this works as it should :-

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

And here is some calling code - line 285 is not necessary while 'Main' is a static member / method.

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

And the eNotes published output for us to compare with what we have created in X# :-

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

We can now compare our output below, with that from Microsoft shown above :-

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

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

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

PHH - 22nd. February 2018.


GetType() - how to use it ...

Okay then folks, we need to look into how the method 'Type.GetType()' works, and what it can do for us.

Here are a couple of small but important things that Help Viewer 2.2 has to say :-

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

The Type 'name' is the assembly-qualified name of the type to get.

If the type is in the currently executing assembly, or in Mscorlib.dll, it is sufficient to supply the type name alone,

qualified by its namespace.

So before we do lots of tests and experiments, lets say that we are trying to supply a string value as input parameter

to the 'GetType()' method and get back in return a know 'Type'.

Yes, we have to realise the the operator 'typeof' does a similar thing BUT not with a string as such, it takes an object

of one of many that are possible - and returns its type.

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

Using it .... With the .NET Framework :

Lets take some advice from the eNotes and try a simple type to get us going.

We will try 'int' / integer, as in 'Int32' - which is in the 'System' namespace. Check this out below :-

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

Yes, it works just fine and we see a count displayed for the possible members of the Int32 class.

The small image below reminds us that when we use the 'typeof' operator we do NOT use quotes :-

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

Both of the above work, and so we could code :-

var myType := typeof(System.Int32)

as an alternative.

But since I was planning on an interactive WPF form which allows user typed text input, we need to

see if we can get the string input parameter approach to work fully for us.

Lets try another common class / type - the 'string' type :-

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

That seems just fine - great. BUT - what about the 'case sensitive' statement in the eNotes ?

Lets try variations of the text string 'System.String' and see what we get :-

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

Oh! - dear it does not work! - we do have case sensitive behaviour after all, and the X# insensitivity does not apply

here, as it is with the Framework members that the issues appears to be - and not X# code.

Take it from me that I tried a few combinations of 'system' and 'string' with upper / lower case, and

only the 'System.String' combination worked, and gave me a listed members count display.

We will need to take case sensitivity into account when we do other things related to the Type methods and

operators as well I feel.

Okay then folks, lets try something a bit less common as 'Int32' and 'String', lets try with Array.

Check this out below, and study the numbers shown in the itemised display :-

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

So in the above image we have the case text settings as required (and correct) and all seems a-okay.

Now then guys, what if we try a class outside of 'mscorlib' and 'System' ?

How about trying 'Color' as we have been using it not long ago in a previous section I was writing ?

Lets just try the fully qualified Class name :-

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

Oh dear ! we have our first failure when we have expected a positive result.

Having had failure with a forced case incorrect text string - we need to check the case details - see below :-

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

No, all seems okay when we compare the above detail with our code - maybe we need to follow advice

from a 'StackOverflow' article and provide two parameters - namespace+class and then also the assembly :-

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

Well folks, I have tried all that I know at the moment - thrown everything I can think of at the problem - BUT - still

no success.

I even tried something silly and got the same level of success - 'none' see below :-

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

Obviously, something is going wrong in the attempted conversion of the string / text input.

Looks like I need the help of the Forum Team guys.

However, one last experiment before I go and do that, lets see is we can have a two parameter input

for a previously simple working test - lets go back to 'Int32' and try two parameters :-

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

Yes, we have a positive result, so there seems to be nothing wrong with our general approach, or

understanding of how it ought to work (should work) !?

Using it ... With our own defined Classes :

For the moment lets leave our little problem and move on to Classes of our own making.

First look at the PRG file headers of the WPF 'Window' class I will use for testing :-

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

And before we start a test we need to see the code of the defined small Class called 'ColorNameValue' :-

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

Okay then, ready to go ? Lets make the text / string changes and compile and run :-

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

Arrh! - good, at least we seem to be able to test our own classes - as simple as it is.

So lets see how the DataGrid control looks when we bind the Collection of methods - the object is 'myMethods2'

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

Remember the above is test code, and so some of it seems superfluous to our main aim / purpose.

Here is where the binding takes place - study the following carefully :-

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

And the above binding produces this output display :-

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

It is only the DataGrid control in which we are really interested.

Time to move on ... if you have not already done so go and checkout the previous sub-section on

the 'typeof' operator.

See you in the next subsection .....................

PHH - 22nd. & 23rd. February 2018.


What is returned by 'GetMethod', 'GetProperty', etc., etc.. ?

Lets try some experiments with the existing code structure we have been using up till now in this section.

By striping off one of the Collection elements we can see what it is.

Check out these details :-

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

And it is therefore quite easy to try and see what this Class is made up from EXCEPT that when I

try I get the message that we can't 'get at' the Runtime version, so I tried 'MethodInfo' which I know from

previous work exists.

Study these details carefully :-

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

If we just bind the Collection returned to a new 'DataGrid' we will be able to see a lot of new information.

Here goes - its a bit wide ;-0)

You need to do this experiment for yourself, so that you can use the scroll bars to do a thorough investigation.

Good Luck !

Once we have the Collection of Methods returned from GetMethods() we can bind it to the DataGrid control.

Here is the pretty standard, and simple, way to do the binding :-

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

And all I have done to display the new details is reorganise the drop box window and slide on

a new DataGrid control from the toolbox - and add the few characters to produce the binding - see below :-

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

And here it is a bit clearer to read :-

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

From this experiment we can possibly predict that there will also be classes for MemberInfo, PropertyInfo, and so on.

How close they are to being 'similar' we would need to see by digging deeper.

I will leave that up to you, I have found out enough for now and wish to move on ................

See you in the next sub-section, to learn some more fun stuff ;-0)

PHH - 22nd. February 2018.


Navigation: 'Reflection' - understanding and using >

Return to chapter overview

Possible uses - suggested by a standard Developer

PHH - 14th. December 2017.


Some more examples of use

What is 'Reflection' to the standard sort of developer guy - like me for example.

Well, it seems to me that it is a way that we can use code to find out more about existing classes.

For us folks it is a way to find out about details of assemblies and what they contain, as well as the

members of a class we care to specify.

The example I will show later is a typical simple but powerful example of 'Reflection' in action.

Before we look at my experimental code, lets see what the Help Viewer 2.2 has to say on a search :-

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

And here is a bit more detail, although some of it may be a bit 'high powered' for some of us! Me included.

But lets run our eyes over the information as a start to our learning 'quest' :-

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

Probably we need to see what is contained within the 'Reflection' namespace.

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

As well as seeing the overall structure of the Help Viewer content :-

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

Lets have a bit of fun and use Reflection to look inside the Reflection namespace, or more correctly the

assembly containing this.

This is probably a better way to handle and see all the various Classes within the namespace.

Here are a few more important class entries in the eNotes list :-

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

Moving forward, and solving our own sample problem :

There is a Structure called 'Color' in the 'System.Drawing' namespace.

This has lots (about 150) properties which are effectively 'named' colors.

See below to see a drop list on a WPF form which shows some of these displayed :-

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

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

PHH - 14th. December 2017.


Navigation: 'Reflection' - understanding and using >

Return to chapter overview

Further examples

PHH - 14th. December 2017.


Interactive app - Assemblies, Namespaces, Types, Members ...

This section of the eNotes is an interesting one for guys who like to get their hands dirty - meaning get typing and make things happen.

We are going to study how to start from nothing and build up a small app which will use Reflection to find and examine Assemblies.

Then after selecting a specific Assembly, have the Namespaces of this Assembly appear in a second drop list.

We will then move on to select a specific Namespace and then display the Types found within that Namespace.

If we are lucky, we will get to be able to select a Type, and then see members of the selected Type, in a final drop list.

While doing the necessary coding we will try to use the most modern style of coding. From WPF forms and data binding,

to LINQ statements to process and tidy the Collections we create when querying the Framework.

Here is what I have just done to get us on our way - we have found all the .NET 4.5.2 dlls for the .NET Framework :-

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

Yes, I know, the full file paths look 'messy' - BUT - we will need them to be able to load the selected

item from the list shown.

Below we see the filename, stripped of its path for easier reading :-

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

So lets now look at the code which looks into the hard coded folder to find the large number of Assemblies.

Lets start with the main part - the STATIC method which processes the files and the filenames coming from the target folder.

Checkout these few lines of code - they do a lot for just a few lines :-

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

A future possible improvement could be to go look for and select a folder to replace lines 40/41.

And we now need to see the supporting small Class for the LINQ output - see below :-

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

And working back the the main Class, and the calling code to activate the app, study the following :-

[ .. note the USING statements with the specific Namespaces .. ]

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

The two data binding lines at position 27 and 28 will possible change as the app develops. But it is fine

for the way the app is at the moment.

We now need to see some XAML script to see the other side of the data binding.

For the present, we are keeping the XAML script quite simple, so we can see what the import parts do for the app.

Study the details and then after the image we can discuss what is going on :-

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

Using the 'DataGrid' control to select an assembly :

Okay then folks, we need to be able to store in a public property the selected item (or value) of the row chosen within the DataGrid.

Here is a selection - we just need to add the correct code to handle Public Properties within the Main Window class.

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

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

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

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

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

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

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

From selected Namespace to a Collection of Types :

From this second 'DataGrid' control we are to be able to select a row, or Collection item, and then have

another (third) control fill with the Types found from the selected Namespace.

So we should get Classes, Enums, etc., .......

PHH - 26th. February 2018.


Arrays

*** BE AWARE ***

Arrays in .NET can form a HUGE topic in their own right.

Most of us do not need to know the full details behind their working.

We need to know the best way to use Arrays in the world of .NET 4.5 and above.

Lets also use LINQ to get the most from strongly typed Arrays.

Many things that we used arrays for in the past, can now be better done using a specific Collection class.

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

Most programmer-developers are familiar with Arrays, and how to use them in code.

Many developers actually 'love' Arrays.

Historically, when 'Fortran' and 'Algol' were still fresh from their wrappings, arrays were the only type

of data structure allowing us to have a 'group' of data items. I mean as opposed to single numeric

types. So lots of us guys got into the habit of using arrays for just about everything ;-0)

And I think its true to say that Arrays are the reason that For/Next loops were invented ;-0)

For loops are how we accessed the data items we placed in our arrays.

And for modern times (now) possibly some guys 'over-use' arrays, when there are now better technologies

available for dealing with groups of 'things' - lists and groups of objects.

We need to 'reserve' our need to reach out for Arrays ONLY when we do 'definitely' need to use them.

When there is not a better alternative.

When most of us got 'well into' using Arrays there were no 'objects' for us to use. Yes, in some ways

Arrays belong to the world of "before object programming" - pre OOP days.

Now that modern .NET classes allow us to use lists (or collections) of objects easily, without runtime

performance penalties, we need to maximise our 'Collection use' and cut back on when we use Arrays.

Some of us, me included, probably don't need to use arrays at all - except when .NET input parameters

need to be arrays, or supply Array data returns.

For many of us we use Arrays because we are comfortable with then, and know how to get them to work

for us, and our client's applications.

For me, getting into the world of 'Data Binding' and WPF has made me use .NET Collections and even get

to like them - a lot. I rarely use Arrays these day, and sometimes that is because an overload of a useful

Method requires it. More on this later - see below :-

Example of needing to use Arrays :

Here below is a coded example of when we must use an array as an input parameter :-

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

Line 143 above uses the character array declared and initialised earlier.

If we look at all the various overloads of the 'Split' method (seen below) you will notice that we

need to use either a String or a Character array in each and every one - meaning that in order for

us to use 'Split()' we need to use an array - no way out!

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

So the summary to this small section is that we all need to know how to create and use .NET

arrays in our coding, even if we generally prefer 'Collections'.

Lets move on .............. se you in the next sub-section.

PHH - 24th. October & 10th. November 2017


VO arrays

Lets get one thing right from the very start - VO arrays are quite different objects to .NET arrays.

They are two quite different 'Class'es.

We can't mix one form with the other HOWEVER they can happily live 'along side' each other and we could

also write code to convert one to the other and back again I would guess.

Warning - I gave up the use of VO arrays well over ten years ago. So I am going to write about .NET stuff in

this sub-section.

VO arrays exist in X#, they have to, that was part of the xBase projects with Vulcan, and then XSharp.

However, if we are to bring our applications over into the world of .NET then any new code we write should try

and use .NET (System) array objects, or better still .NET Collections like 'List<T>'.

All you need to know about VO arrays can be found in the standard references for VO, and Alwyn's

book, the one I call the 'Black Bible' because of its colour and nature ;-0)

There is no need for me to say anything more EXCEPT that the VO texts will not be able to warn you

of the basic and fundamental difference between VO arrays and those of .NET available to us within X#.

History !!!

Where we come from :-

From what I remember some ten years back, VO arrays were easy to use and we could do some smart

things with code blocks for sorting and filtering.

Arrays in .NET seem at first to be more awkward to use in code for a few reasons - BUT - they also now

have the Great BIG advantage that we can use LINQ with them - that is a great PLUS when it comes to

sorting and filtering etc., etc.. I would say this is 'THE' reason to move from VO arrays to .NET arrays whenever

you can.

Lets now move on and learn something .NET and useful ;-0)

PHH - 24th. October 2017


.NET arrays

.NET arrays - these are sometimes referred to as 'System' arrays, simply because this was the original namespace

for them 'System'.

If we do a search in Help Viewer 2.2 we see this :-

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

And also this :-

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

Few Properties BUT lots of Static Methods :-

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

*** NOTE *** :-

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

>>> So this means we can use LINQ statements/code with .NET arrays !!!

Here are the interface memberss which are implemented :-

Here are all of the members of 'IList' interface for the Array class :-

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

The System.Array class has been around from very early days of .NET, and according to the details in

Help Viewer 2.2 (seen below) we see that version 1.1 seems to be the first appearance of Arrays.

Check this out below :-

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

We need to read on to find out more useful knowledge :-

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

It is such a pity that Arrays (see above) are or predetermined length - fixed size that is.

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

The final few words shown below, do not worry me as we can do all our sorting and filtering using LINQ in today's programming world.

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

In fact I tend to do all my array processing (these days) with LINQ statements.

Time to move on ................

Lets go and learn some more useful stuff.

PHH - 24th. October 2017


The misunderstood 'ZERO' - taking another look !

When it comes to 'ZERO' and the base for indices in .NET arrays, the poor zero has been

totally misunderstood in my current view of things.

Yes, I have totally changed my mind, very recently, and think I now am understanding what

Microsoft did for us in providing a way to instantiate an 'N' dimensional array in .NET.

We guys have wrongly been focusing on the 'zero or one base' question when in fact there is

no issue at all - just our lack of imagination I feel.

I will provide code and a Class to get around any zero based issues.

I also strongly suggest that you select in properties the option to have a zero base for the index,

in place of a one base.

The setting mention above only applies to .NET arrays, and only those which are declared and defined

in the code we write.

Any arrays made by the .NET framework as returned data from members will be by default zero based.

So to my mind, why make life difficult for ourselves.

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

PHH - 21st. November 2017 * 2nd. April 2018..


My own 'Personal' opinion of Arrays in .NET

I was good with VO arrays, and liked them a lot, and used them all over the place.

However, when I coded in VO some ten years ago now, I don't think I used Object Programming

in the way I do now.

Because I use Collections in place of arrays (whenever I am able) I did not realise how awkward

and old fashioned they (arrays) feel in the world of .NET.

I embraced .NET many years ago and have liked it for many, many reasons - Arrays is not one of

these I am afraid.

Be aware that Arrays were built into the very earliest versions of .NET and for this reason I feel they

did not get the syntax to be as we now feel that .NET syntax should be.

I use arrays when I must, like for Framework input parameters, and avoid them when I can.

Collections and LINQ are wonderful technologies, so I use them at all times.

Still, if we are professionals we need to know when and how to use .NET arrays.

So lets get cracking and come to terms with them, and their rather odd syntax.

We can use System.Arrays without the need to use the square bracket ( ' [ ] ' ) syntax.

Check my 3D array sample to see this in action.

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

PHH - 10th. November 2017.


FOR loops - good, bad, or what !?

For / Next loops were provided in code syntax so that we could access array elements via the square bracket syntax.

The historic array syntax of ' [ n ] ' needs For loops.

In today's .NET OOP coding environment there are alternatives to this, and in my opinion superior alternatives.

Check this out that I found recently :-

https://advancedweb.hu/2017/11/21/a_detailed_case_study_against_for/

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

I personally agree with this sentiment AND I practice what I preach.

Here is some more on the topic :-

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

Okay then guys, this is where we are headed - check below :-

[ .. a toffee apple (treat) for the first person to find or spot a 'FOR' loop ... ]

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

The above code is processing a List<Customer> object, but it could be just as easily an Array of Customers.

Study below to see a modern coding approach to an old 'construct' :-

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

Now then folks, this is NOT an excuse to keep using arrays, just a way to explain why we can use LINQ 100% of the time.

We are still limited by the fixed size of arrays after declaration.

I much prefer 'List<Customer>' for my GUI and data binding etc., etc..

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

PHH - 29th. & 30th. November 2017.


FOR / NEXT

Okay then, for the sake of comparison we need to see how to use the For / Next construct, even

though there are better things to use these days.

Remember that the For loop handled the index value, but these days we can avoid using indexes.

We can do the following without need to use 'i' for index.

First lets checkout the FOR loop as an index value manager :-

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

Okay, most of us are familiar with this style of coding.

Lets now move on to the next section where we start to modernise our approach.

See you there ...........

PHH - 30th. November 2017.


Foreach / Next

In this subsection we will see FOREACH used to manage array element access - BUT - there will

be no sign at all of the index or its value.

Study this carefully :-

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

So no ' i ' or index and yet we have the same sort of result.

Lets get even more modern in the next subsection .......

PHH - 30th. November 2017.


Enumeration

There is another way of moving through an array without specific use of the 'i' or index value.

We can use an 'enumerator' - this can be used an a variety of grouped objects.

Checkout this simple approach :-

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

I suggest you research further into Enumerators.

And now to the next sub-section which is even more modern in its approach .....

PHH - 30th. November 2017.


LINQ querying

Okay guys, lets have some real fun, and explore a few modern ways of using and dealing with .NET Arrays.

Before we do our manipulation, we need to create a suitable array :-

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

So lets do some useful (but simple) manipulation - checkout carefully :-

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

Yes, above we are employing some method syntax code as well as a Lambda Expression in line 48.

The above code sums the elements with values which are even.

Now we can sum the odd values as seen below :-

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

Check for yourselves if the sums are correct.

lets try a change of syntax style ...........

'Query Syntax' or 'sugar' syntax :-

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

Microsoft recommend we use 'Query Syntax' whenever possible.

The above code is more like T-SQL script.

However, some things can't be done in 'QS' and at least some of the statement needs to be in method syntax.

Here below we use the 'Sum()' extension method :-

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

There are many extension methods, providing some very useful functions for us.

However, while we are showing this sample, lets see an alternative way to code the same thing :-

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

Yes, we can split up our query statement into parts, this is particularly helpful for longer and more complex queries.

And now to view our full replacement for the FOR loop we started with - check this out :-

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

And line 158 introduces us to AT's (anonymous types).

'CLASS' means we are making an un-named class out of the three data properties specified.

More on AT's later - they make life easy in some parts of our code.

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

PHH - 30th. November 2017.


Advice from Microsoft

I found the following graphic as good advice from Microsoft, while searching through material

in the 'Help Viewer 2.2' eNotes.

I will explain what it means after you have seen the simple details, check below :-

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

The first column is the "I want to ..." and the second one is which Collection to use.

It is generally considered that we should in .NET coding be using Generic collection types

whenever we can.

I also personally consider this translates to .... "Only use arrays where there is no other option."

Over recent months (and years?) I seemed to focus on this approach myself and it is good.

Although many of us who use List<T> very regularly, possibly only a fraction of us use the indexing

ability (or functionality) of the generic List class.

The availability of LINQ has probably removed the last few excuses for using the index approach to storing

and searching data in memory.

I suggest that VERY few examples of array and list use, have any direct connection between the index

value and the data element stored in that array cell. The 'i' value is meaningless, so why use the Array ?

Finally, there is a VERY good reason for using List<T> in place of Object[] and that is that the length of

the List is not restricted, and we can keep adding more new elements as the code executes.

Getting away from a fixed length .NET Array is certainly good in my eyes.

Here below is a Collection being accessed by index - quite unusual code for me ;-0)

The List<T> collection could have been used in place of ObservableCollection<T> :-

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

I will leave it up to you to do more exploration on this way to use / access Collections.

Good experimenting ...............

PHH - 29th. November 2017.


Single dimensional Arrays

Okay then guys, lets start simply.

Lets try and create a one dimensional array of numbers (integers).

Here below is some working X# code with a few lines of LINQ to process the array :-

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

[ .. lines 53 - 56 are all one statement so we need the semi colons included .. ]

Lines 50 and 51 declare and initialize an array of length 5 - with the values shown.

One of the problems with arrays in .NET is that they need to be of a fixed length - something many VO guys hate!

Now for our first BIG issue to get our heads around :-

*** CARE *** with settings in project property for ZERO based Arrays !!

This caught me out after doing a sequence of code trials and testing :-

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

It should have been as seen below, note the zero '0' on line 388 below :-

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

It would seem that the issue of zero versus one based arrays will live with us forever. So get to grips with it ASAP.

My advice is when at all possible try to use zero based arrays as this is the .NET default, and that

seen in C# example code on the web that we will often need to copy and adapt etc..

Okay then folks, lets try some objects in our one dimensional array.

Below is a simple car class - check it out :-

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

Now we need to declare and initialize our array - below is one way to do this :-

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

[ .. the semi colons allow us to split the line to make it readable .. ]

The above code means we end up with an array of length 5, each element being a Car object.

We can use LINQ to filter and sort on the properties / fields of the simple Car class :-

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

So we seem to be doing all right with our index addressing, that is using the '[ ]' syntax with an

appropriate index value for gaining access.

Okay, line 82 could be a bit more interesting BUT I was just testing the basic code structure.

Here is an improvement I feel :-

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

Yep! - works well enough I feel.

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

Alternative declaration / initialization syntax :

We can actually declare a new array to be of INT and length 7 elements.

This is done before we actually assign any elements - checkout below :-

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

Even though we have neglected to assign (or fill) element positions 5 and 6, the LINQ code still works okay.

Lets now see what is in the unassigned element positions. Lets try element 6 in position 5 because we have

ZERO based arrays set in the project property options / settings.

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

It would appear that the elements have been initialised to '0' (zero) at time of compiling and running.

We could have been excused to think that a default 'content' of 'nil' may have been set.

So what happens when we use an object class like 'Car' in a similar manner to above ?

Okay then guys, I have just done some testing and found out some useful things / tips.

Lets first of all declare a new 'Car' array and then only partially initialise it :-

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

I suspect that the error in the code sample above is caused by line 58 as seen below.

The code is trying to access and element which has not been initialised :-

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

If we change this to assign default 'empty' Cars using the 'blank' class constructor, we get

the following working code :-

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

Here is the blank Constructor from my simple Car class :-

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

If we try and assign 'null' s instead we get another error :-

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

Yes, 'null' is not an instance of 'Car' !

So what if we initialise the whole new array with a default Car object - see below :-

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

So to summarise, we can access the array element, but not use its unassigned value.

I suggest that we take care to initialise all our arrays carefully, do enough testing. This will

then stop us getting errors further into our application development.

It would also appear that in order to use FOREACH we do need items / elements to be assigned to the

particular 'cell' of the array object.

PHH - 9th. & 10th. November 2017.


Array length - "the bad news !"

Okay guys, we have to live with this, but its not all bad ;-0)

.NET arrays can't be used in a way we may all like.

We can't have an array populated with elements / items, and then add an extra item to the array.

Meaning we can't increase the length of the populated array by somehow adding a suitable extra element.

Many of us converted VO guys would have liked to be able to do this.

In .NET the 'List<T>' class allows us to do such things, using the Add and AddRange methods.

As to the 'ArrayList' class, this can be added to as well.

So we have to learn how to get around this apparently annoying situation ;-0)

There are ways .. read on ...........

PHH - 12th. November 2017.


Initialising all Array elements - various ways

*** NOTE *** a good rule of thumb >>

Whenever possible initialise the contents of all declared array 'cells'.

Think of the array items / elements as the contents of the 'cells'.

If all cells have values within them then fewer runtime issues can and will occur.

Examples of items / contents are INT values, STRING values, and 'Car' objects.

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

Okay guys, we have seen how an array declared for 'Car' objects needs to be initialised so that

we don't have 'empty' or 'blank' elements anywhere in the array - or we are likely to generate runtime errors.

We need to improve the VERY simple code we used previously - well, in another sub-section close to this one.

This is what I used previously :-

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

It would be nicer if we did not use a fixed length value for the array bounds.

At the moment I am using the lower bound as zero ' 0 ' because the project property setting is set TRUE for

"Use Zero Based Arrays".

Hopefully we can code the initialising code to handle both settings, as well as the top / upper bound being found

from a property of the array.

Lets start with the upper bound first, its a bit easier.

Lets now write a FOR loop to handle ZERO based and any array length. Study the following image details :-

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

The above code now uses the 'Length' property of the array to set the upper bound. This is more general

than the code we used on lines 152 through 154.

The message box shows that the code still compiles and works correctly.

What we REALLY need to do now is handle the array base by finding the (zero base) project property setting and then

adjusting our code to use the result. If we do this then the code will always work for us ;-0)

Lets take this one step at a time, and set the True/False for the property in our code, then adjust the rest

of the code to fit in with this. Study what I have done so far in the image below :-

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

Yes, the code appears to work for a ZERO based setting. Lets now try the one base - BUT - I need to

remember to change the actual project property and that may screw up some of my other code.

I will give it a go and if I hits issues than I will make a new small project for the testing purposes.

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

Yes, as I thought the default setting shown below causes an error to be thrown - see details below :-

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

Before we see and learn how to use the settings property, lets do a hard code check with a TRUE value :

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

I will leave you to do this trial as it is relatively straight forward.

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

And what now happens when we have set 'TRUE' ?

Accessing the system property value - how its done.

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

Okay guys, now that we know how to handle the index base setting - find and use it - lets move

on to doing some other useful 'stuff'.

On our array lets filter with a more complex expression / condition :-

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

Lets see what our Car class looks like - a reminder from a previous section :-

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

In fact the above Class needs to be changed in order to make it resistant to errors in use.

Our instantiation needs to be more robust - see below for an error which needs removing / eradicating :-

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

All we need do is improve the 'blank' or 'empty' constructor - see the following :-

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

Now we can run it in a similar way to before, when we got an error :-

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

And with a couple of other line changes it reads more meaningfully in the 'MessageBox' - check this :-

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

You need to spend some time playing around with code similar to mine above.

We only really learn when we type in examples and run them, then further enhance them, etc., etc..

Good luck with you trialling and testing ...........

PHH - 11th. November 2017.


Various ways of accessing the array elements

"Indexes or Not" :

We can access the contents of array 'cells' by using their index values. This can be thought

of as the 'classic way' to use arrays.

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

Often we access many array elements at once by using a FOR / NEXT loop and an incrementing index value.

It is true to say that FOR loops were created (invented?) to allow access to arrays.

So below we can get at all the elements one after another :-

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

However, in .NET we can also use a FOREACH loop when we do not need to use the index syntax, and

instead an enumerator does the work for us.

Study this way :-

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

So we get access to the elements without need to use indexes and index values.

There are other interesting approaches seen in other sub-sections .........

PHH - 12th. November 2017.


Fixed length arrays

When we declare a fixed length array we specify the size we require it to be.

The compiler will then specify sufficient space to be available to hold all of the elements required.

At run time this causes a structure to be set up in memory such that a 'cell' is created to be

able to take what is effectively an item of data, this is for each of the fixed number of elements / items.

If the array is of a simple data type then it would also appear that the cells have an initial default value.

In the example shown below we can see the values of cells specified by my code - BUT - cell with index

value '5' has not been 'filled' or specified by my code, and yet it prints out as zero '0'. Check thoroughly :-

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

Does this also apply to type Double ?

Yes, it would appear to - check this out below :-

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

And to show that the rest of the method works okay - check this below :-

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

Well, everything above seems to be okay, and in order.

BUT - what about STRING elements ? These seem to be different - check this sample shown below :-

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

But if we try to trap the NULL array elements we get success, 'a runner' ! See below :-

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

LINQ coded statements are good at trapping or handling 'null's.

Lets write and use a LINQ query to count the number of elements in the string array.

First we need to view and study the above code lines to be able to predict the resultant count :-

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

Remember that we are counting the elements in the array even though some are still set to 'null'.

But we can use 'FOREACH' to examine the elements as seen below :-

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

For examples using user defined Classes checkout the child section from here.

This will demonstrate with examples using the Car class of mine.

After some practice we get good at knowing how to avoid mistakes / errors, and how to get what we are after.

It would appear to be a good idea to make each string element an empty string, so as to avoid issues.

Go and code some tests and trials ..............

PHH - 12th. November 2017.


Possible runtime issues / errors

Okay folks, it is time to consider how we may cause ourselves some grief when coding with 'Fixed' arrays.

Using the fixed array declaration similar to that seen below :-

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

we can happily fill up array 'cells' with data items.

However, we may start to cause problems if we leave some 'cells' empty, or unassigned, and then try

to use these later as data items.

This is why I like to only create 'cells' at the time I also provide some data content.

A 'fail-safe' if you prefer to think of it as such.

I am not sure if all data types will cause us such issues, but certainly some do, I know as I have had some

related problems.

I will see if I can code up an example to show you.

Yes, okay then guys, we have an example of 'grief' !

Checkout this code below and see just what a simple change does making the fixed value 9 instead of 5 :-

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

Putting the value on line 419 back to 5 we get the code to complete the iteration and supply a Count value :-

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

At the moment I am not sure if it is possible to trap the unassigned cell contents, as it depends

on how the FOREACH structure works.

More tests required ...........

Yep! - we have a possible solution.

We need to do a test in the FOREACH loop to check that the returned iterative object is not NULL.

Here is what is working for a fixed length of 9 where 4 cell items have not been assigned :-

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

Line 429 tests to see whether the returned iterative item is not 'NULL'.

All unassigned array elements with have a NULL content.

We do need to be well aware of this possible issue, and deal effectively with it.

Good Luck !!!

PHH - 17th. November 2017.

Addendum : Counting the Nulls :

Oh! I forgot to add that we can in fact count and record the Nulls we find in our array.

Here below is a way to find the array elements (or their content) that are Null.

We could adapt this code to find and record the index values of the Nulls.

First study the easy code to weed out the Nulls and miss trying to process them :-

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

Well, the code does correctly catch and count the NULL elements in the array of length 9.

Lets move on to another subsection ............

PHH - 17th. November 2017.


Dynamic length arrays - meaning ?

Here is what C-sharpCorner has to say :-

http://www.c-sharpcorner.com/article/working-with-arrays-in-C-Sharp/

The article is by a renowned author whom I admire greatly - he is 'Mahesh Chand'.

He says in the article :-

"In .NET, arrays can be declared as fixed length or dynamic.

A fixed length array can store a predefined number of items / elements.

A dynamic array does not have a predefined size.

The size of a dynamic array increases as you add new items to the array.

You can declare an array of fixed length or dynamic.

You can even change a dynamic array to static / fixed after it is defined."

Unfortunately, the same article does not actually show how to add extra items / elements

to the array once it has been initialised.

I personally think that we have some confusion due to the words and phrases used to explain things.

We need to experiment with X# code I feel ;-0)

Okay then folks, I think I have worked things out for us.

It is the length of the array which is 'dynamic' and not the contents during runtime.

(And even then I question the wording!)

Here is my example for us to discuss :-

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

On line 68 we have what may be described as a 'dynamic' array declaration.

So 'myDynamicArray' can be assigned a string array of any length, at runtime.

So the four different arrays on lines 72 through 75 are all allowable, and won't cause an error to be thrown.

Obviously, we can form the string array to be assigned to our dynamic array in quite a few different ways.

Using List<T> and the 'ToArray()' method is very common in code. But the same can be done from other

Collection types.

So the description of 'Dynamic' really only applies to a named Array object being re-assigned at runtime.

This is NOT what VO guys would accept as being correct 'wording' I suspect. I for one think this is

a bit of a fiddle.

I hope this helps you understand what is going on.

PHH - 12th. November 2017.

Addendum :

It would appear that things may even be a bit more crazy when we talk of Fixed & Dynamic arrays - as

seen in my comments above.

We need some more testing with experimental code.

Lets declare an array to be four '4' items / elements long - a fixed array !?

Then later in our code assign arrays of varying length to this fixed array, some assignments being with

other arrays longer than 4 items in length.

Check in details the following couple of images :-

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

Okay, the above issue / error is to be expected as the current array length at line number 75 is actually 4 so the

maximum index value should be 3.

But if we continue in our code execution we see line 80 assigning an array of length 6 (six) to the one we

previously declared as length 4.

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

We could think of the above behaviour as odd, if we get fixated by the term 'fixed' as used by others.

Personally, I feel it is quite okay and that we should just see the code like this :-

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

As used by the compiler to first count the declared items and then make and fill a length 3 array, as

a fixed array - nothing fancy or complicated at all.

Certainly the wording of 'dynamic' is nothing further from the truth, as I see things.

So when we assign an array to one already declared, we have a new length array made of the same length

/ size as the assigning data.

So in the above code we see arrays of size/length 3,4,1, and 6 being assigned, and this means that 'myDynamicArray'

has each of these sizes, until it is reassigned to yet another and different array.

Why do folks have to complicate things when they try and explain stuff ? I think it is pretty straight forward.

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

PHH - 12th. & 17th. November 2017.


Practical differences between "Fixed and Dynamic"

After quite some extensive work and testing I think I now have a workable understanding of what is

meant in .NET about 'Fixed' and 'Dynamic' arrays.

It simply all to do with how they are declared, and which array 'cells' exist at runtime before other

lines of code are run.

To put it bluntly, there is not really a BIG difference, and the use of these terms would make some

of us think there is much more too it than there really is.

The example below is simply a useful coders short-hand for declaring and filling (at one go) an array

of size / length 4.

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

Any word or term 'dynamic' is VERY misleading I feel.

All we are doing is getting the compiler to do some work for us, and make a fixed array at compile time.

If we add another string to the declaration input parameters then at the next compile time the compiler

counts an extra item and adjust the length required.

Why on earth have they made so much fuss about this ?

So in my books, this supposed 'dynamic' code is a simple way to declare and fill a simple and short array.

Handy you may say!

Lets move on to more important things ..............

PHH - 12th. & 17th. November 2017.


Arrays to Lists

Okay then folks, we usually wish to turn a List object into an array object, BUT, if it can be done

the other way around then we need to know how to do it in as smart a way as possible.

Study the code below carefully - see how we turn a Car array into a List<Car>, a strongly typed list :-

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

With the Project property for 'Use Zero Based Arrays' set to 0 (and not 1) ... effectively TRUE .... we can

write the above test method using the simple car class we made earlier.

Checkout line 320 and notice that we have 'data cells' 5,6,7 & 8 unassigned.

*** WARNING *** Unassigned array 'cells' can cause issue at runtime !!!

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

Okay then guys, it looks like your turn to go and do some typing.

Make your own test and trials, it's the best way to learn.

PHH - 15th. November 2017.


LINQ query to Array

There may be times that we have the data as a result of a LINQ query, and we need this data to be

in the form of an Array.

Here is a way which seems to work nicely for us :-

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

Obviously line 511 is the one which does what we need / require.

Lets take this a little further lets have two arrays as seen below :-

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

And now study the following image code to see how these can be used and filled :-

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

Once again, you need to read this carefully and then try some coding yourselves.

Do try tests and trials for yourself, its the best way to learn ;-0)

PHH - 15th. November 2017.


Making our own Array - full Index control, and more !

Here is what a few of you guys will be thinking .........

What is the point of trying to do 'our own thing' as regards to .NET arrays ?

Well, there are a few simple but good answers, not the least being that we will learn a

lot about X# syntax as well as learning in more depth some of the .NET features available to us.

Making a wrapper to go around a System.Array is a great chance to employ a bit of our own Genetics.

By this I mean we can code some Generics as opposed to just using it in cases like 'List<T>'.

Lets look at how we may code things such that we can use a Generic array type of our own.

Check this out fully, line 100 in particular :-

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

In the above code we have a user defined Type called "MyXIndexArray", being used for the STRING type.

We could just as well have used it for other types like INT, Double, DateTime, and even user defined classes

like my simple 'Car' class.

Lets see the same class used for integers :-

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

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

PHH - 20th. November 2017.


The simplest specification of a Generic Type

In this subsection we will look at the simplest declaration / definition of a 'wrapper' class for our

single dimensional array with user defined index bounds.

I have not included any more complex features which we will see later in further definitions / declarations.

For example the method 'Add' is not included, or 'FillRange'.

We do however maintain the method to find all elements in the array!

Checkout the general structure / form of our Generic class, and the names of the methods included :-

[ .. I am aware that the Properties could / should be defined in a bit more of a secure way ! .. ]

[ .. remember, we are trying to keep this as simple as possible, until we get the hang of things ..]

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

Notice that most methods are to do with index values - in, out and transformed - and the elemental

'values' - values in and out of an array 'cell'.

Lets now see this simple wrapper class in action, before we study the code details of the methods :-

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

Work out for yourself that 'Triumph' is correct ;-0)

Handling our new Index value range :

Okay then guys, as you can imagine, we need to be careful when we translate our index values to and from

those required by the underlying .NET zero based array, into our own specified index limits.

Our code shown below allows values to be negative as well as positive, and both lower and upper index values

(limits) may be negative.

What we MUST be careful not to do is calculate an index value which is not valid - THEREFORE - we need to

do a "bounds check" each time we do a conversion - lets call them 'outside' and 'inside'.

Outside index values are what our code see, like for example -5 through 0 to +5.

Inside index values are zero based .NET ones, typically of 0 through to N.

Obviously the two index ranges are coupled via a simple mathematical conversion formula.

First lets see the checking code - method named 'CheckIndex' :-

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

Any index value being passed into out wrapper object from the outside needs to be checked to

see if it falls within the lower / upper bounds we have stored in our properties.

Its then up to us to handle any unacceptable values.

Now then, if we have a suitable value of an outside index value, how do we convert this to a

suitable value for use 'inside' the wrapper object ?

Below we see the off-set value being applied to carry out the conversion :-

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

While we are considering the index value conversion, lets see what we need to do

the conversion the other way.

What do we need to do to inside index values to create a suitable 'outside' value ?

Study the following code carefully :-

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

Once again its just a matter of applying the off-set value in the appropriate manner.

Okay then folks, we have dealt with index values, and their transformation or conversion.

What about the actual values of the array elements themselves ?

Remember we are trying to write 'wrapper' code to handle all sorts of types of objects.

Checkout below the 'T' shown in line 594, it specifies the type of the element which is

being provided to be stored in the array :-

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

Obviously above, we need to take in the element value (or content) as well as the linear index value itself.

The index value is then check and converted, before the element is stored in the array.

You can see some code which I will use to track (in some simple way) which element was last input, as well

as the highest position in the linear array of any stored item.

I am not quite sure as yet as to what I will include in my element checking procedure and methodology.

Now then, what about trying to retrieve an item from the elemental array ?

Checkout below the 'T' shown in lines 615, and 616. These specify the type of element

object to be retrieved and returned :-

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

Note above the syntax form of the 'casting' taking place on line 621.

All of the above code seems to be necessary - AND - it seems to work well under testing.

Before we move on to the necessary but slightly mundane issues of Constructors and Initializers,

lets take a moment to see the more apparently useful method which finds all array elements for us.

Check out the full details, carefully, of the following tested and working method :-

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

Note line 632 can't be used any more, as this was previously there when the array items were numeric.

When we have a truly Generic class for our array 'wrapper' then we can only handle 'NULLs'.

Complex business objects can be NULL and generally it is the value of properties on the class object that we

wish to compare in our LINQ queries.

Okay then - now to the important but less glamorous code - lets check the Initialization first :-

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

In the above code it is line 571 which is the important and critical one, this creates the .NET array.

Note that on line 571 we need to pass through to the static method the type of the Generic 'T' - this

is crucial to success.

Obviously, in the above code, we need line 575 to allow us to do the index value conversions correctly.

Transforming the index value from 'Out to In' as well as 'In to Out'.

Now then in the following image see line 539 for the Generic 'T' specification :-

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

The Constructors are reasonably obvious in what they do.

Obviously we need to transform the index bounds correctly, both lower and upper bounds.

So we are effectively applying the array bounds required to find the length of the new zero based

.NET array which will be hidden in the 'wrapper'.

You can ignore the empty Constructor, I just like to have one that takes no input parameters - habit!

Well folks, that's it, easier than we may have thought - AND - if we can write/create a wrapper for a

single dimension array, then we can also make a Generic wrapper for a 2-D one as well ;-0)

Checkout another subsection for the 2-D version. Or even write one yourself.

Oh! and don't forget I also intend to create and write a more complex version of the single dimensional

array wrapper. A version which has Add, FillRange, Resize and a bunch of more useful stuff.

Remember, this array stuff is more of a way of learning a whole bunch of stuff, than it is a serious suggestion

as to what we use in our coding.

My overall suggestion is use other Generic collections types first, before you reach for Arrays.

PHH - 20th. November 2017.


Working X# code

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

PUBLIC CLASS MyXIndexArrayS<T>

public property myType as Type auto

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

property IndexLower AS INT auto

PROPERTY IndexUpper AS INT AUTO

PROPERTY length AS INT AUTO

PROPERTY myArray AS System.Array AUTO

PROPERTY nOffSet AS INT AUTO

PROPERTY nRequired AS INT AUTO

PROPERTY nIndex AS INT AUTO

PROPERTY nLastIn AS INT AUTO

PROPERTY nHighestIn AS INT AUTO

PUBLIC CONSTRUCTOR()

self:myType := typeof(T)

SELF:IndexLower := -5

SELF:IndexUpper := +5

SELF:length := IndexUpper - IndexLower + 1

self:myInitialize()

RETURN

PUBLIC CONSTRUCTOR(nL as int, nU as int)

self:myType := typeof(T)

SELF:IndexLower := nL

self:IndexUpper := nU

SELF:length := IndexUpper - IndexLower + 1

self:myInitialize()

RETURN

PUBLIC METHOD myInitialize() AS VOID

SELF:nLastIn := -9999 //--- not perfect but shows nothing stored yet ---

SELF:nHighestIn := -9999

SELF:myArray := Array.CreateInstance(self:myType, SELF:length)

//--- will access array for zero base index ---

//--- make IndexLower from zero ---

SELF:nOffSet := SELF:IndexLower

MessageBox.Show(SELF:length.ToString(),"Length test :")

RETURN

PUBLIC METHOD CheckIndex(nZ AS INT) AS Boolean

local lOK := False as Boolean

IF nZ >= SELF:IndexLower && nZ <= SELF:IndexUpper

lOK := True

endif

RETURN lOK

METHOD TransIndexOut2In(IndexOut as int) AS INT

LOCAL retIndex AS INT

//--- do conversion ---

retIndex := IndexOut - nOffSet

RETURN retIndex

METHOD TransIndexIn2Out(IndexIn AS INT) AS INT

LOCAL retIndex AS INT

//--- do conversion ---

retIndex := IndexIn + nOffSet

RETURN retIndex

PUBLIC METHOD InVal(nVal AS T, nI AS INT) AS VOID

LOCAL IMPLIED Val := nVal

IF CheckIndex(nI)

//--- apply off-set

nRequired := TransIndexOut2In(nI)

//--- set the value

myArray.SetValue(Val, nRequired)

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

SELF:nLastIn := nRequired

IF nRequired > SELF:nHighestIn

SELF:nHighestIn := nRequired

ENDIF

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

ENDIF

RETURN

PUBLIC METHOD OutVal(nI as int) AS T

LOCAL nResult AS T

IF CheckIndex(nI)

//--- apply off-set

nRequired := TransIndexOut2In(nI)

//--- retrieve the value

nResult := (T)MyArray.GetValue(nRequired)

ENDIF

RETURN nResult

PUBLIC METHOD FindAllElements() AS T[]

LOCAL aINT AS T[]

//--- will return all elements !

LOCAL aList AS List<T>

aList := List<T>{}

FOREACH item AS T IN myArray

IF item <> NULL

//if item <> 0

aList.Add(item)

//endif

endif

next

aINT := aList.ToArray()

RETURN aINT

END CLASS

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

try

LOCAL IMPLIED mxia3 := MyXIndexArrayS<Car>{-39, -26} //--- from, to ---

local implied Car1 := Car{"Triumph","Red",1500}

local implied Car2 := Car{"Citroen","Light Blue",2000}

local implied Car3 := Car{"Jaguar","Black",3000}

local implied Car4 := Car{"Ferrari","Silver", 5500}

local implied Car5 := Car{"Fiat","White",500}

mxia3:InVal(Car5, -26)

mxia3:InVal(Car1, -33)

mxia3:InVal(Car2, -31)

mxia3:InVal(Car3, -29)

mxia3:InVal(Car4, -39)

local implied myResult3 := mxia3:OutVal(-33)

MessageBox.Show(myResult3.Make.ToString(),"Retrieved value :")

catch oEX as SystemException

MessageBox.Show(oEX.Message,"my Error testing :")

end try

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

PHH - 20th. November 2017.


Navigation: Arrays > .NET arrays > Single dimensional Arrays > Making our own Array - full Index control, and more ! >

Return to chapter overview

More complex and useful array 'wrapper' class

PHH - 20th. November 2017.


Using a single demensioned array to store 3D data

Okay guys, this is an interesting topic, although these days (modern times) we do not need to do

'clever tricks' in order to get the programming facilities we we for our high level programming.

There was a time in the past when certain computer languages had limitations in the array facilities

they offered - then these 'tricks' were a necessity.

Okay then, an array is a 'group' data type. At the least it provides us with a single dimension starting

at a base index value. Probably 1 or 0 - said to be one based, or zero based.

As long as we have the above facilities then we can construct for ourselves anything more complex

that we need.

Lets see if we can use the .NET facility for a single dimension with base zero, to make a 'wrapper' class

to provide us with a 3-D array where the three indexes can each be separately defined to be of range

-N through to +N.

When we code our high level business code we will use three coordinate inputs, but the 'wrapper' class

we design and create will in fact use the three coordinate inputs and convert this to only one, which will

be zero based for the 'wrappered' array we will be using.

At least in doing this exercise we will get some good coding practise with X# syntax, as well as the .NET

array class.

Okay then folks, lets move on to make some 'action' ...........

Making a start :

Lets start near the end, always a good place to start I feel ;-0)

Let us see some 'action' code and a limited display of a result.

Below is a small section of test code which uses an array class I have defined.

Line 34 shows how we instantiate an array of the 3D type called "My3Din1D_array", as well as then

use it to store a test value which is then retrieved.

Check the detail out carefully :-

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

Lines 40 and 41 show where we store and retrieve a value of type STRING.

Because we have designed the 3D array to be Generic, we can in fact use any Type we choose.

Now then, looking at the code above more closely - line 34 - we see that the X index ranges from 5

though to 13.

We also see that the Y index range is 10 through 20, and the Z index -30 through -15.

In fact the wrapper class array can have all 3 indexes ranging from -N through 0 to +N - meaning

any integer range we choose. We can also start or stop any of the three ranges at 0, as this is just

another integer value in the possible range.

Finally, before we move on to study the code in detail, lets see above the result MessageBox which shows

us that the input value has been retrieved.

Below we see the line at 204 which produced the test output - even though now, as it stands, the line

has been commented out for general use :-

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

Lets now move on a pace (step) or two ..............

Converting index values :

Okay, lets face it, the biggest issue we have with this 3D to 1D 'wrapper' class is converting the coordinate, or

input values to the correct internal value, and then converting back the other way, a single internal value into the

three values seen by the outside of the wrapper environment.

Only the code inside the wrapper class methods see a single coordinate value which has a zero based index system.

Let us now study the code which converts the X,Y, and Z (or R,C, and S) values to one internal value.

[ ... I like to think of Rows, Columns and Sheets - hence R,C, & S ... ]

Study the image details shown below - thoroughly :-

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

The above method converts three input values (integers) in the order of input R,C,S (or X,Y,Z) into

a single value which the code above names 'retIndex' (for return index value).

Now then folks, I have to confess it is numerically, or mathematically, 'tricky' to write and understand

the formulae used in the conversion.

If you are interested in deeply understanding what is going on then at least you need to appreciate that

a 'Sheet' contains R * C array points (cells).

We can come back to this later on, for now lets see the following code in action.

Notice we are to use Fabrice as our test sample - check this carefully :-

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

Line 32 above creates and stores for us a 'Person' object based on Fabrice and him being French.

We are using the 3D 'cell' at coordinate points 13, 20 and -15 to store the data.

Obviously, we need the two conversion methods to get the maths right so that we read and write

data, to and from the same array cell. Below is what I see at runtime :-

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

When I uncomment the relevant code lines in InVal (lines 134 and 135) we can see the internal

values relating to our single index - here is the sum of the parts from the three input index parameters :-

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

And now we can see the three numerical parts which sum to make the single index value :-

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

While I was perfecting the formulae it was helpful to make test 'runs' using the extreme values of the

input parameters - the lowest and the highest. Here is what I tried :-

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

And here are the results displayed with some more details which will help explain stuff :-

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

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

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

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

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

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

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

PHH - 28th. November 2017.


working code and script

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

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.Navigation

using System.Windows.Shapes

USING System.Collections

USING System.Collections.ObjectModel

USING System.IO

#define CRLF e"\r\n"

BEGIN NAMESPACE WPF3Din1D_array

PUBLIC PARTIAL CLASS WPFWindow1 INHERIT Window

  PUBLIC CONSTRUCTOR()

       InitializeComponent()

       MyInitialize()

return

  public method MyInitialize() as void

      local myTestArray as My3Din1D_array<Person>

       myTestArray := My3Din1D_array<Person>{5,13,10,20,-30,-15}

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

      try

          //=== try an input and a retrieve ! ===

          local myPerson := Person{"Fabrice","French"} as Person

           myTestArray.InVal(myPerson,13,20,-15)

           myTestArray.OutVal(13,20,-15)

           myTestArray.InVal(Person{"Tomas","German"},12,20,-15)

           myTestArray.InVal(Person{"Robert","Dutch"},13,19,-15)

           myTestArray.InVal(Person{"Henry","English"},13,20,-16)

          //=========== try extremes of coordinates ============

           myTestArray.InVal(Person{"Benjamin","Welsh"},5,10,-30)

           myTestArray.InVal(Person{"Laura","Welsh"},13,20,-15)

          self:dg3in1.DataContext := myTestArray.FindAllElements()

      catch oEX as SystemException

           MessageBox.Show(oEX.Message,"my Error test Person :")

      end try

  return

end class

public class My3Din1D_array<T>

  public property RowIndexStart as int auto

  public property RowIndexEnd as int auto

  public property ColumnIndexStart as int auto

  public property ColumnIndexEnd as int auto

  public property SheetIndexStart as int auto

  public property SheetIndexEnd as int auto

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

  public property RowIndexLength as int auto

  public property ColumnIndexLength as int auto

  public property SheetIndexLength as int auto

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

  public property Total3D_gridNumber as int auto

  public property myArray as System.Array auto

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

  public property NumberOnSheet as int auto

  public property NumberInColumn as int auto

  public constructor(nRS as int, nRE as int, nCS as int, nCE as int, nSS as int, nSE as int)

      if self:CheckInputIndexRanges(nRS, nRE, nCS, nCE, nSS, nSE)

          self:RowIndexStart := nRS

          self:RowIndexEnd := nRE

          self:ColumnIndexStart := nCS

          self:ColumnIndexEnd := nCE

          self:SheetIndexStart := nSS

          self:SheetIndexEnd := nSE

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

          self:MyInitialize()

      else

           MessageBox.Show("At least one range check is incorrect ...","my Error - check ranges :")

      endif

  return

  public method CheckInputIndexRanges(nRS as int, nRE as int, nCS as int, nCE as int, nSS as int, nSE as int) as Boolean

      local lCheck := TRUE as Boolean

      if nRE <= nRS

           lCheck := FALSE

      endif

      if nCE <= nCS

           lCheck := FALSE

      endif

      if nSE <= nSS

           lCheck := FALSE

      endif

  return lCheck

  public method MyInitialize() as void

      self:RowIndexLength := self:RowIndexEnd-self:RowIndexStart+1

      self:ColumnIndexLength := self:ColumnIndexEnd-self:ColumnIndexStart+1

      self:SheetIndexLength := self:SheetIndexEnd-self:SheetIndexStart+1

      self:Total3D_gridNumber := self:RowIndexLength * self:ColumnIndexLength * self:SheetIndexLength

      //MessageBox.Show(self:Total3D_gridNumber.ToString(),"Total 3D grid points / cells :")

      //MessageBox.Show(self:RowIndexLength.ToString(),"Row index length :")

      //MessageBox.Show(self:ColumnIndexLength.ToString(),"Column index length :")

      //MessageBox.Show(self:SheetIndexLength.ToString(),"Sheet index length :")

      local cText := "" as string

       cText += self:RowIndexLength.ToString()+" Row index length :" + CRLF

       cText += self:ColumnIndexLength.ToString()+" Column index length :" + CRLF

       cText += self:SheetIndexLength.ToString()+" Sheet index length :" + CRLF

      //MessageBox.Show(cText,"All three ranges :")

      self:myArray := Array.CreateInstance(typeof(T), self:Total3D_gridNumber)

      //--- it will access array for zero base index ---

      self:NumberOnSheet := self:RowIndexLength * self:ColumnIndexLength

      self:NumberInColumn := self:RowIndexLength

  return

  method TransThreeIndexOut2OneIn(RowIndexOut as int, ColumnIndexOut as int, SheetIndexOut as int) as int

      LOCAL retIndex AS INT

      //--- do conversion ---

      local SheetsFull := (SheetIndexOut-self:SheetIndexStart)*self:NumberOnSheet as int

      local ColumnsFull := (ColumnIndexOut-self:ColumnIndexStart)*self:NumberInColumn as int

      local RowsPart := RowIndexOut-self:RowIndexStart as int

       retIndex := SheetsFull + ColumnsFull + RowsPart

      //MessageBox.Show(retIndex.ToString(),"Inside index :")

      //MessageBox.Show(SheetsFull.ToString() + " " + ColumnsFull.ToString() + " " + RowsPart.ToString(),"Each index :")

  return retIndex

  method TransOneIndexIn2ThreeOut(IndexIn as int) as int[]

  local retIndex as int[]

      try

           retIndex := int[]{3}

          //--- do conversion ---

          local FullSheets := IndexIn/self:NumberOnSheet + self:SheetIndexStart as int

          local RemainderSheets := IndexIn % self:NumberOnSheet as int

          local FullColumns := RemainderSheets/self:NumberInColumn + self:ColumnIndexStart as int

          local RemainderColumns := RemainderSheets % self:NumberInColumn + self:RowIndexStart as int

           retIndex[0] := FullSheets

           retIndex[1] := FullColumns

           retIndex[2] := RemainderColumns

      catch oEX as SystemException

           MessageBox.Show(oEX.Message,"my Error inside '1in3out'")

      end try

  return retIndex

  PUBLIC METHOD CheckRowIndex(nZ AS INT) AS Boolean

      local lOK := False as Boolean

      IF nZ >= SELF:RowIndexStart && nZ <= SELF:RowIndexEnd

           lOK := True

      endif

  RETURN lOK

  PUBLIC METHOD CheckColumnIndex(nZ AS INT) AS Boolean

      local lOK := False as Boolean

      IF nZ >= SELF:ColumnIndexStart && nZ <= SELF:ColumnIndexEnd

           lOK := True

      endif

  RETURN lOK

  PUBLIC METHOD CheckSheetIndex(nZ AS INT) AS Boolean

      local lOK := False as Boolean

      IF nZ >= SELF:SheetIndexStart && nZ <= SELF:SheetIndexEnd

           lOK := True

      endif

  RETURN lOK

  PUBLIC METHOD InVal(inVal AS T, RI AS INT, CI as int, SI as int) AS VOID

      LOCAL IMPLIED Val := inVal

      IF CheckRowIndex(RI) && CheckColumnIndex(CI) && CheckSheetIndex(SI)

          //--- apply off-set

          local implied nInsideIndex := TransThreeIndexOut2OneIn(RI, CI, SI)

          local implied CellRequired := nInsideIndex

           myArray.SetValue(Val, CellRequired)

          //MessageBox.Show(Val.ToString(),"stored ?! :")

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

      else

           MessageBox.Show("Either Row, Column, or Sheet is not within bounds!","Outside stated bounds :")

      ENDIF

  RETURN

  PUBLIC METHOD OutVal(RI as int, CI as int, SI as int) AS T

      local Result as T

      IF CheckRowIndex(RI) && CheckColumnIndex(CI) && CheckSheetIndex(SI)

          //--- apply off-set

          local implied nInsideIndex := TransThreeIndexOut2OneIn(RI, CI, SI)

          local implied CellRequired := nInsideIndex

          //--- retrieve the value ---

           Result := (T)MyArray.GetValue(CellRequired)

          //MessageBox.Show(Result.ToString(),"Out result ... :")

      ENDIF

  return Result

  PUBLIC METHOD FindAllElements() AS T[]

      LOCAL aItems AS T[]

      //--- will return all elements !

      LOCAL aList AS List<T>

       aList := List<T>{}

      FOREACH item AS T IN myArray

          IF item <> NULL

               aList.Add((T)item)

              //MessageBox.Show(item.ToString(),"words ? ")

          endif

      next

       aItems:= aList.ToArray()

  RETURN aItems

end class

CLASS Car

  PUBLIC property Make AS STRING auto

  PUBLIC property Colour AS STRING auto

  PUBLIC property EngineSize AS INT auto

  PUBLIC CONSTRUCTOR()

      SELF.Make := "...m/t..."

      SELF.Colour := "...m/t..."

      self.EngineSize := 0

  RETURN

  PUBLIC CONSTRUCTOR(cM AS STRING, cC AS STRING, nES AS INT)

      SELF.Make := cM

      SELF.Colour := cC

      self.EngineSize := nES

  RETURN

  virtual method ToString() as string

  return self.Make.ToString()+" : "+self.Colour.ToString()

end class

CLASS Person

  PUBLIC property Name AS STRING auto

  PUBLIC property Nationality AS STRING auto

  PUBLIC CONSTRUCTOR()

      SELF.Name := "...m/t..."

      SELF.Nationality := "...m/t..."

  RETURN

  PUBLIC CONSTRUCTOR(cNM AS STRING, cNT AS STRING)

      SELF.Name := cNM

      SELF.Nationality := cNT

  RETURN

  virtual method ToString() as string

  return self.Name.ToString()+" : "+self.Nationality.ToString()

end class

END NAMESPACE

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

<Window x:Class="WPF3Din1D_array.WPFWindow1"

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

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

      Title="my 3D in 1D array :" Height="350" Width="525" Background="Cornsilk">

  <Grid>

      <DataGrid Name="dg3in1" HorizontalAlignment="Left" Margin="19,26,0,0"

                VerticalAlignment="Top" Height="167" Width="138"

                ItemsSource="{Binding}"/>

  </Grid>

</Window>

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

CLASS Person

  PUBLIC property Name AS STRING auto

  public property Nationality as string auto

  public property InternalIndex as int auto

  PUBLIC CONSTRUCTOR()

      SELF.Name := "...m/t..."

      SELF.Nationality := "...m/t..."

  RETURN

  PUBLIC CONSTRUCTOR(cNM AS STRING, cNT AS STRING)

      SELF.Name := cNM

      self.Nationality := cNT

  RETURN

  virtual method ToString() as string

  return self.Name.ToString()+" : "+self.Nationality.ToString()

end class

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

Alternative testing 'MyInitialize' :

  public method MyInitialize() as void

      local myTestArray as My3Din1D_array<Person>

       myTestArray := My3Din1D_array<Person>{5,13,10,20,-30,-15}

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

      try

          local nInVal as int

          //=== try an input and a retrieve ! ===

          local myPerson := Person{"Fabrice","French"} as Person

           nInVal := myTestArray.InVal(myPerson,13,20,-15)

           myPerson.InternalIndex := nInVal

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

           myTestArray.OutVal(13,20,-15)

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

           myPerson := Person{"Tomas","German"}

           nInVal:= myTestArray.InVal(myPerson,12,20,-15)

           myPerson.InternalIndex := nInVal

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

           myPerson := Person{"Robert","Dutch"}

           nInVal:= myTestArray.InVal(myPerson,13,19,-15)

           myPerson.InternalIndex := nInVal

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

           myPerson := Person{"Henry","English"}

           nInVal:= myTestArray.InVal(myPerson,13,20,-16)

           myPerson.InternalIndex := nInVal

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

           myPerson := Person{}

           nInVal:= myTestArray.InVal(myPerson,12,19,-17)

           myPerson.InternalIndex := nInVal

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

          //=========== try extremes of coordinates ============

           myPerson := Person{"Benjamin","Welsh"}

           nInVal:= myTestArray.InVal(myPerson,5,10,-30)

           myPerson.InternalIndex := nInVal

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

           myPerson := Person{"Laura","Welsh"}

           nInVal:= myTestArray.InVal(myPerson,13,20,-19)

           myPerson.InternalIndex := nInVal

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

          self:dg3in1.DataContext := myTestArray.FindAllElements()

      catch oEX as SystemException

           MessageBox.Show(oEX.Message,"my Error test Person :")

      end try

  return

PHH - 29th. November 2017.


Multi-dimentional (rectangular) Arrays

Having seen how to create and use a single array of items / elements, we can now move on

to see how we can create and use a Multi-Dimensional array.

The multi dimensional array is also often called a 'Rectangular' array, simply because it has

the 'shape' of a matrix, where the length of each 'row' is the same, or constant.

It is good to think of a 2D 'rectangular' array as a square, or rectangular matrix of elements, or cells.

We will need two indices to access the array elements. Lets call these 'r' and 'c' for row and column.

So in the example below we have 3 rows and two columns (dimensions) - checkout the syntax :-

[ *** note *** 13 is an arbitrary value, my birthday and lucky number ... ]

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

So in the above example, we can use indexing to assign the cell 'zero, zero' with the value '13'.

Lets now try and assign and access the topmost cell - row 3 and column 2 - but with zero based arrays the

numbers (INTs) to use are 2 and 1

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

We need to know how to initialize a 2D (matrix) array at declaration time.

So far I have been struggling just a bit ;-0(

PHH - 9th. November 2017.


"Jagged Arrays in .NET"

A 'jagged' array is simply an array of arrays.

The elements of a 'jagged' array are other arrays.

The array elements can be of different sizes, or lengths in the simplest case.

Lets try our first example - check this out :-

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

Here our jagged array is an array of string arrays.

Since an Array is strongly typed then the content arrays have to be all the same - in this case STRING arrays.

Let us tidy the code a little :-

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

Now then, as you can see we have not got the individual elements being displayed, so we

need to change our code to solve the problem.

How about this then ? :-

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

Okay, that seems better ;-0)

So what if we have variously length STRING arrays (hence the name 'jagged') - check below :-

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

That looks even better - the code shown above.

BUT then guys, what if we wish to mix content arrays of different types ? How do we do that ?

Lets remember some of what we learned about OOP and Three Pillars - just the Polymorphism pillar this time.

Study this carefully :-

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

Well, it works, sort of, mixing string, integer and double values - since all inherit from type/class Object.

However, if we wish to have a heterogeneous mixture of arrays, then we need to use the 'ArrayList' class.

System.Arrays are homogeneous, meaning the same, and 'ArrayList' are heterogeneous.

PHH - 8th. & 16th. November 2017.


Mixed Arrays

Good News - GREAT news - On less thing to worry about for us guys !!!

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

This (mixed arrays) is obviously thought by Microsoft to be now unnecessary in the modern world of .NET code design.

As from .NET version 4.0 it has been removed / dropped.

Apparently, even 'Mahesh Chand' did not see this type of array used much at all.

Mixed arrays are a combination of multi-dimension arrays and jagged arrays.

Most probably first included because some guy could technical 'do it' ! Not because they are of need to the

high level programmer.

You can do anything you want with the help of multi-dimensional and jagged arrays.

Personally, I feel that the time of the Array is past and that many / most things are better achieved using

well designed object Classes and newer .NET collections.

Certainly using classes of objects and collections the data held in object properties is far more easily accessed.

Time to move on ...............

See you in the next subsection.

PHH - 9th. & 17th. November 2017.


LINQ - processing Arrays

By far one of the most exciting 'things' about .NET arrays as used in today's coding world, is that LINQ

technology can be applied to them, or used on them.

Here below is a simple example which we saw earlier, in a previous sub-section. Lines 53 through

57 can be considered the LINQ part - check thoroughly below :-

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

Note that in line 61 we need to convert the resultant collection into an array - this eases further processing.

To make good sense of the code to come below, refresh your mind with the structure / design of the 'Car' class

we saw in earlier subsections :-

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

Lets now see a section of code using a Car array and making a LINQ query - check below :-

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

This is called from the following code :-

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

And now we need to see the LINQ code and the way we may retrieve the resultant elements :-

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

Remember it is always useful to convert the resultant collection from the LINQ statement into a simpler

collection or array. Line 81 above does this for us.

Finally, something not very exciting but necessary - printing the object elements found :-

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

Lets now add a line of code to see the object type of the LINQ result collection :-

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

And if you wish to be able to pass back the results array from out of a Method, it is best to convert

the results data into a simpler Array object, as in line 81.

So here is the amended method which passes back some 'Car' array data :-

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

If you wish to know more about LINQ then look at my more extensive eNotes called 'ClickStartLINQ', as well

as recent session material I prepared for Cologne 2016 & 2017.

Lets move on ............ see you in the next subsection.

PHH - 7th. & 10th. November 2017.


Accessing the Array items / elements

To many guys the whole point, or main attraction, of arrays is the simple and easy access we

get to array elements via the indexing system.

One dimensional arrays have a single index, and 2D arrays have two indexes. Obviously, the more

dimensions an array has the greater the number of indices.

This seems simple enough until we come across the following ;-0((

Indexes - zero based or one based ! ?

It may surprise you guys to know that when I started my main frame computing the Algol 60 interpreter

allowed us to use negative index values. These worked very well and on VERY large and technical programs too.

Our issue with .NET, and coming from VO, is that VO guys got to know and love one based indexing. That

simply means that arrays always started with a '1' as the lowest bound, or index value.

However, .NET in all its wisdom is ZERO based.

It gets worse, since we now have in X# the choice of using in our projects, either zero OR one. But not

both in one project.

Check out this image of the properties of my Address Book app :-

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

The above code sets the index base to be '1' and so the following code works :-

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

If we change the properties setting to TRUE - as below :-

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

Then we need to change the code to the following to get it to run correctly :-

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

Since I came from VO a long time ago, and all my Vulcan code was .NET oriented, I will go wit the C# was and

choose and use ZERO based. Not because I prefer it, simply to take the easiest approach for .NET coding, when

we as developers often have to refer to C# example code.

Here is the ZERO based code working :-

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

Yes, I know, lines 103 and 104 would not normally be written in this simple fixed index value way ;-0)

The above code is just test / trial stuff - not a style to be copied exactly.

Lets move on, see you in the next subsection ..................

PHH - 9th. & 10th. November 2017.


'Style of coding' with arrays

Okay then guys, in many ways Arrays are old fashioned.

Possibly it is more correct to say that the style of coding using Arrays can be old fashioned,

as well as more modern in its approach, if we do things right.

If we use an array as a receptacle, or container, in which to place and store data items, then

this is better generally than having the 'position' of the array element matter - or have some

meaning as such.

PHH - 13th. November 2017.


By index value

Unless you are using the index value in your array as something meaningful, like a unique ID,

then I suggest you give up using indexes at all, and use iteration and LINQ instead.

Basically, it is now considered old style, past fashion, even NOT good programming technique,

to have meaning coded (or built) into your indexing system.

However, they may well be a few cases which are okay, like my sample Customer Survey analysis.

So having warned you NOT to use them in MOST cases, lets see how we can access array element

values / data by use of indices.

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

We can access an array item by passing the item index into the array.

This is probably the oldest 'historic' way to access array elements, I was doing this at the end of 1969.

The index values we are passing in the code below are 0 through 4.

In my example as shown, the index value does not relate in any way to the contents - meaning the index

and the element are not related in any meaningful way.

Check this out - elements are being 'stored' :-

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

Once we have an array initialised with elements, we can access them for retrieval as shown below :-

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

In the above code it is line 237 in which we are particularly interested.

We now have to deal with a 'chestnut' of an idea - one which can screw our code if not handled right.

What is the base value of our indices ? Well, with a setting on the project we can choose zero or one.

Although VO and in some ways Vulcan, used 1 as a base, I suggest that at all times possible you use zero.

.NET has a zero base for all indices so we would be better to "go with the flow". List<T> objects are also

zero based.

Before you get carried away with the wish to argue about this, bare in mind that it is VERY easy to write a

'wrapper' class which allows us to define and specify our own bases for any on 'N' dimensions.

We can write code to access the property setting, so that our code can be made 'bomb proof' as far as 0 / 1

is concerned.

Here is how the property settings page looks like :-

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

Line 236 in the previous image (one before last) shows the use of 'ArrayLowerBound()' and 'ArrayUpperBound()'

as static methods.

These two methods when used can protect us against runtime code errors. Study the details carefully :-

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

If we do not use these 'protection' methods then we could have working and tested code become

'broken' at run time (or uncompilable) just by changing a single property setting ! ;-0((

More on this topic later (other subsections) but now lets continue to view code based on using

index values to access stored data elements.

Here below, is a method from the Customer Survey application, which has three dimension to its

main array which is used for summing up the questionnaire responses - checkout the details :-

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

Yes, above we have lots of i's, j's, and k's. Not my liking these days

Although another subsection deals with more modern ways to access array elements, lets

at least take a peek at what 'might be'.

Newer way and BETTER ??? :

Lets re-write the code to check my customer survey results - notice we have no "i,j,k" stuff.

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

The above looks better, and in fact it can be simplified even further - to see this look to the

subsection two on from here and entitled 'By LINQ querying'.

Sometimes there is a definite need for index 'looping' .......... if the index values are related to the

contents / elements in some way, and we need to store them, as opposed to retrieve them.

To store our elements correctly we must know and use the index values - check below for my code

to initialize the 'Survey Results' array :-

[ Lets look at the problem / task of initially filling the 3D grid with elements of the type 'XYZ_GridPoint'. ]

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

PHH - 13th. November 2017.


By iteration

The FOREACH control statement (loop) is used to iterate through the items of an array, without need

for our code to know or use any index values.

This approach came to be available for our xBase code after that using Index values.

So in one way or another is more modern than indexing alone.

Often this approach is used in a "Read Forward" manner. Meaning we move forward from start to finish,

and only read and use the array elements.

If we use FOREACH iteration we work around the issue of needing to know if the index is zero or one based.

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

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

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

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

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

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

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

PHH - 13th. November 2017.


By LINQ querying

This is by far the most modern approach for accessing array elements. It came well after the

inclusion of the FOREACH control statement in .NET.

In fact LINQ is not widely used by xBase programmer guys for some reason I can't figure out.

I have personally embraced LINQ usage, BUT, this could be because I first fully embraced the

SQL approach to searching through data. I mentally gave up the idea of needing to know where

my data was stored, so moving to LINQ was easier for me.

LINQ is powerful and VERY flexible indeed.

Good News - if we use LINQ to access array elements (data items) then we do not need to worry

about zero or one based indexing.

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

From the look of the above LINQ code it would appear that it is effectively iterating. It is going through

the whole bunch of items in 'myStringArray' and processing in some way.

The above result of the LINQ iteration is a collection of elements which we above entitled 'item'.

Obviously, the result of a LINQ iteration is a collection of the same length, or shorter.

Yes, the result may be no items at all, depending on the filtering conditions.

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

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

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

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

PHH - 13th. November 2017.


Using arrays without any indexes at all

It is easy to see how we can code applications to use existing arrays without the need to reach

out for indices.

However, it is not so obvious to see how we can create arrays in the first place without index access

to the array elements.

Lets take a simple example which earlier we did the old fashioned or classical way - check below :-

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

The above code works in my test application simply because I have the Project property of 'Use Zero Based Arrays'

set to a value of TRUE.

If I change the setting to one of 'False' then my code would fail.

So how do we code the above WITHOUT using array indices ?

The answer is to use something other than an Array, in this case a List<T> but it could be another similar

Collection, like Stack<T> or Queue<T>.

Lets see how this looks when re-coded :-

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

Yes, we see code that does make an array called 'myStringArray' and this has elements of the

same values as previously shown.

However, this time I have used a List<T> class object to allow the code to add successive items to the list

without call for using index access. Yes, List objects provide index access too.

Here is some image proof :-

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

The index for List<T> and other similar collections, is in fact zero based - BUT - the Property setting

of True/False only affects Arrays. So List[3] will always be 'three' no matter what the 'Zero Based Arrays'

setting happens to be. I just tested it for good measure ;-0)

PHH - 13th. November 2017.


What is different in their coded use

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

Where is the 'Constructor' section above ?

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

As seen above, most class sections in the 'HelpViewer' eNotes have a 'Constructor' section after 'Syntax'

and before 'Properties'.

So we have no public constructor to use in the usual way of things ;-0((

Instead we do the following :-

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

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

And here is what the eNotes said about creating an instance of an Array :-

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

PHH - 26th. October 2017


Creating an Instance !?

Okay then guys, this sounds simple enough, or is it ?

If we look into the "Help Viewer 2.2" and find the 'System.Array' class we see that there are a few static

methods which are entitled 'CreateInstance'.

BUT - what does this actually mean ? What type of object is returned etc., etc..

Lets see which these are - check below :-

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

Okay then, lets check-out the first of these to see how it works.

Let us try our X# code and an example where we create a 2D array, 2 by 3 of Strings.

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

When we create an instance as shown above, we then need to use a method called 'SetValue' to access the individual

array 'cells'.

Checkout the following simple sample code :-

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

As you can see above we use 'SetValue' to 'write' an array value and 'GetValue' to read one. If we are using

the 'Read/Write' metaphor for array element access.

Lets try and access an array element in the normal way - see below :-

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

Yes, if you are like me, then you will find this rather confusing - even though we are experienced enough to cope with it.

I can make this work for me if I have to !

Personally, I find it far less confusing to use Collections whenever possible. I hardly use Arrays of any sort.

This is an interesting comment from the Microsoft eNotes :-

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

Lets now try and write some test X# code to fill the array cells with some string values.

I am going to follow a sample from the Microsoft help notes for C# ...............

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

Here is my X# version, with a few improvements :-

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

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

Checkout line 123 where we set the array cell value :-

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

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

PHH - 9th. November 2017.


3-D array example

Okay folks, we have seen so far how the 'Array' class in the 'System' namespace does not have a

Constructor.

But we have seen that STATIC methods exist on the Array class to allow us to create and Instance

of the Array class - an object.

Having been previously successful wit a 2D testing / trial example, lets see if our X# code handles 3D

as well, and as easily.

In our example shown below we will call our three indices 'i, j, and k'.

We will also choose to have the dimensions set to 2 by 3 by 4. That is a total of 24 array 'cells'.

Lets first study the code to create the correct array 'form', or structure - see below :-

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

Well, at run time we seem to have created an array object of type 'System.String[,,]' which is in my

language a three dimensional String array.

Lets now study the rest of the working code - check this out :-

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

Note that in lines 154 through 156 the indices are referred to as 0,1, and 2. Our geometrical X Y and Z I suppose.

Here is the output at runtime :-

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

I am still amazed at what three simple lines can do for us - see lines 163 through 165.

The FOREACH iteration is something we should fully embrace in our coding I feel.

Alternative STATIC method - uses index array in place of index value inputs :

Now then guys, there is no point in learning about arrays if we shy away from using them in our code.

Let us now look at an overload of the 'CreateInstance' STATIC method - one which requires an array as

input parameter.

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

Here is what I came up with and tried out :-

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

The above code seems to work just fine, and the rest of the X# method of mine stays the same as before.

Remember that the .NET setting is for ZERO based indices, and only an X# Project setting can change this

to one based.

We really do need to check this for X# I suppose ;-0)

PHH - 14th. November 2017.


Resizing an existing array

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

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

PHH - 18th. November 2017.


The 'thorny' topic - Zero vs One based arrays

On the one hand this is an important topic to discuss, as it affects all of us.

On the other hand it should not be an issue to the high level programmer.

The syntax that you and I use to design and create business applications should allow us to

select and choose any reasonable numeric 'base' to our arrays. Not just zero and one.

For most of us the issue started because our coding in Visual Objects (VO) was one based for

the arrays we made and used.

Moving to Vulcan based on .NET Don Caton had to merge the two worlds of VO and .NET, when .NET

had all its indexes for arrays and collections based on a value of zero - ' 0 '.

PHH - 14th. November 2017.


Our own Generic 'wrapper' for a 2D array

Well guys, after our experience and success with creating a 'wrapper' class for a single dimensioned

array, we should be able to move on to make a similar one for 2D it would appear to me.

All we need do is make sure we handle the index bounds properly and correctly for both of the required

dimensions (indexes). We need to keep our code clear to read, so we don't get any confusions occurring,

either now or later.

...... some time later ...............

Well, it was not a difficult task to copy and upgrade the working single dimension array 'wrapper' to make

it into a 2D one. Just a matter of handling the indexes and conversions correctly.

The actual code line to instantiate a suitable .NET array object is the least of our worries - see below :-

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

As you can see above, the overload for 'CreateInstance' now takes two integer parameters for the now 'two'

dimensions. Lets call these X & Y, or better still for our 2D metaphor, is the 'R & C' for rows and columns.

By the way, I do not intend to make a three dimensional 'wrapper' although it would be quite easy to do so.

These days I have no coding need to use 3D arrays, although I have used them in the past for scientific purposes,

and to good avail (use) too.

I have included all the working code for the 2D 'wrapper' class in a child section to this one.

However, it is now a good idea for us to look at and study in some detail, the methods for passing data elements

both in and out of the array being used.

Lets look at the very important wrapper method to accept data inputs - see below :-

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

The input (or 'in') value has to be placed in position RI, CI - in a coordinate style syntax.

So first we need to convert the user defined, and specified, row and column index vales to those

that are zero based for use with the .NET array.

The 'Trans' methods are all very similar to what we developed and tested in the one dimensional array

wrapper class. All I have done is add 'Row' and 'Column' to the appropriately named methods.

Here is one such translation method to study :-

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

If we look back at the 'InVal()' method we actually see that all line but 551 are related to index checking,

and general management of the 'tracking' variables of the wrapper class - that is those to track the last

input values for row and column, as well as the highest and lowest values of R & C so far used.

We could dispense with a lot of the management code I hear you say - BUT - what is the point of making

a wrapper class if we don't add some extra helpful features and functionality.

*** POSSIBILITIES ***

One thing I noticed in reviewing the code, was that possibly because I came to the 2D wrapper class having

first created and tested a 1-D class, I have dealt with the indexes one at a time. It would be an option to

deal with pairs of values, pairs of R & C values. So for a 2D class we need two array pairs of index values.

I leave it for you do make this sort of useful change ;-0)

Lets now move on a step or two, and see how much simpler the method for outputting a value seems to be

without the need to manage index values for highest, lowest, last in of both R and C.

Checkout these details below :-

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

All we need to do above is supply the user defined index coordinate system values, check them, and then

convert these to zero based values, and the retrieve the correct element of course.

Line 580 does the business, the rest is checking and converting.

Lets go further with 2D than we did with 1D.

Lets act upon the case where either a Row or Column is out of bounds.

Remember that when we code our own array instantiation, we provide max and min index values for each index

of the N-dimensions.

If we ask for input access to matrix elements outside of these bounds then we need to do something, we

can't keep 'stumb' about it ;-0)

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

In the image of input code shown above, we see that I have double typed the '1' so 13 becomes 113.

This takes the coordinates outside of the bounds, for the column at least.

Here is how I now handle the event / situation - it could report better than this, but its a useful start - check below :-

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

Since a row / column pair needs both index values to be correct, we can just about get away with this simple report-back.

If you wish you may rewrite the code to trap which is incorrect and report more details.

PHH - 20th. November 2017.


working code

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

PUBLIC CLASS MyXIndexArray2D<T>

property RowIndexLower AS INT auto

PROPERTY RowIndexUpper AS INT AUTO

property ColumnIndexLower AS INT auto

PROPERTY ColumnIndexUpper AS INT AUTO

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

PROPERTY RowLength AS INT AUTO

PROPERTY ColumnLength AS INT AUTO

PROPERTY myArray AS System.Array AUTO

PROPERTY RowOffSet AS INT AUTO

PROPERTY ColumnOffSet AS INT AUTO

PROPERTY RowRequired AS INT AUTO

PROPERTY ColumnRequired AS INT AUTO

PROPERTY RowIndex AS INT AUTO

PROPERTY ColumnIndex AS INT AUTO

PROPERTY RowLastIn AS INT AUTO

PROPERTY ColumnLastIn AS INT AUTO

PROPERTY RowHighestIn AS INT AUTO

PROPERTY ColumnHighestIn AS INT AUTO

PROPERTY RowLowestIn AS INT AUTO

PROPERTY ColumnLowestIn AS INT AUTO

PUBLIC CONSTRUCTOR()

SELF:RowIndexLower := -5

SELF:RowIndexUpper := +5

SELF:ColumnIndexLower := -10

self:ColumnIndexUpper := +10

self:myInitialize()

RETURN

PUBLIC CONSTRUCTOR(nRL as int, nRU as int, nCL as int, nCU as int)

SELF:RowIndexLower := nRL

SELF:RowIndexUpper := nRU

SELF:ColumnIndexLower := nCL

self:ColumnIndexUpper := nCU

self:myInitialize()

RETURN

PUBLIC METHOD myInitialize() AS VOID

SELF:RowLength := self.RowIndexUpper - self.RowIndexLower + 1

SELF:ColumnLength := self.ColumnIndexUpper - self.ColumnIndexLower + 1

SELF:RowLastIn := -9999 //--- not perfect but shows nothing stored yet ---

SELF:ColumnLastIn := -9999

SELF:RowHighestIn := -9999

SELF:ColumnHighestIn := -9999

SELF:myArray := Array.CreateInstance(typeof(T), SELF:RowLength, self.ColumnLength)

//--- will access array for zero base index ---

//--- make IndexLower from zero ---

SELF:RowOffSet := SELF:RowIndexLower

SELF:ColumnOffSet := SELF:ColumnIndexLower

MessageBox.Show(SELF:RowLength.ToString(),"Row Length test :")

MessageBox.Show(SELF:ColumnLength.ToString(),"Column Length test :")

RETURN

PUBLIC METHOD CheckRowIndex(nZ AS INT) AS Boolean

local lOK := False as Boolean

IF nZ >= SELF:RowIndexLower && nZ <= SELF:RowIndexUpper

lOK := True

endif

RETURN lOK

PUBLIC METHOD CheckColumnIndex(nZ AS INT) AS Boolean

local lOK := False as Boolean

IF nZ >= SELF:ColumnIndexLower && nZ <= SELF:ColumnIndexUpper

lOK := True

endif

RETURN lOK

METHOD TransRowIndexOut2In(RowIndexOut as int) AS INT

LOCAL retIndex AS INT

//--- do conversion ---

retIndex := RowIndexOut - RowOffSet

RETURN retIndex

METHOD TransColumnIndexOut2In(ColumnIndexOut as int) AS INT

LOCAL retIndex AS INT

//--- do conversion ---

retIndex := ColumnIndexOut - ColumnOffSet

RETURN retIndex

METHOD TransRowIndexIn2Out(RowIndexIn AS INT) AS INT

LOCAL retIndex AS INT

//--- do conversion ---

retIndex := RowIndexIn + RowOffSet

RETURN retIndex

METHOD TransColumnIndexIn2Out(ColumnIndexIn AS INT) AS INT

LOCAL retIndex AS INT

//--- do conversion ---

retIndex := ColumnIndexIn + ColumnOffSet

RETURN retIndex

PUBLIC METHOD InVal(inVal AS T, RI AS INT, CI as int) AS VOID

LOCAL IMPLIED Val := inVal

IF CheckRowIndex(RI) && CheckColumnIndex(CI)

//--- apply off-set

RowRequired := TransRowIndexOut2In(RI)

ColumnRequired := TransColumnIndexOut2In(CI)

//--- set the value ---

myArray.SetValue(Val, RowRequired, ColumnRequired)

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

SELF:RowLastIn := RowRequired

SELF:ColumnLastIn := ColumnRequired

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

IF RowRequired > SELF:RowHighestIn

SELF:RowHighestIn := RowRequired

ENDIF

IF ColumnRequired > SELF:ColumnHighestIn

SELF:ColumnHighestIn := ColumnRequired

ENDIF

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

IF RowRequired < SELF:RowLowestIn

SELF:RowLowestIn := RowRequired

ENDIF

IF ColumnRequired < SELF:ColumnLowestIn

SELF:ColumnLowestIn := ColumnRequired

ENDIF

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

ENDIF

RETURN

PUBLIC METHOD OutVal(RI as int, CI as int) AS T

LOCAL Result AS T

IF CheckRowIndex(RI) && CheckColumnIndex(CI)

//--- apply off-set

RowRequired := TransRowIndexOut2In(RI)

ColumnRequired := TransColumnIndexOut2In(CI)

//--- retrieve the value ---

Result := (T)MyArray.GetValue(RowRequired,ColumnRequired)

ENDIF

RETURN Result

PUBLIC METHOD FindAllElements() AS T[]

LOCAL aItems AS T[]

//--- will return all elements !

LOCAL aList AS List<T>

aList := List<T>{}

FOREACH item AS T IN myArray

IF item <> NULL

//if item <> 0

aList.Add(item)

//endif

endif

next

aItems:= aList.ToArray()

RETURN aItems

PUBLIC METHOD ViewAllElements() as void

local cText := "" as string

LOCAL aItems AS T[]

//--- will return all elements !

LOCAL aList AS List<T>

aList := List<T>{}

FOREACH item AS T IN myArray

IF item <> NULL

//if item <> 0

//aList.Add(item)

cText += item.ToString()+CRLF

//endif

endif

next

//aItems := aList.ToArray()

MessageBox.Show(cText,"Viewing elements :")

RETURN

END CLASS

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

PHH - 21st. November 2017.


Typed arrays

The following is what Robert sent me recently while I was having some issues remembering the syntax

for a Character array :-

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

We can go ahead and experiment with this for many types, but first lets see another way which Robert

helped me with, before he sent the above information.

The following is just a test :-

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

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

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

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

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

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

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

PHH - 25th. October 2017


ArrayList class !?

*** CARE / NOTE *** Although the 'ArrayList' class contains the word (or string) 'Array', it is in fact within the

"System.Collection" namespace.

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

The 'ArrayList' class has been around from early times - version 1.1 of .NET in fact.

As you can see below it is in the System.Collections namespace :-

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

*** NOTE ***

Most people like the ArrayList ('AL') class of objects because they can be used and extended naturally, as more items (or elements)

need to be added. There is no need to re-make the AL with a larger length, different maximum index.

For this reason the AL is much more intuitive to use.

Compared with the System.Array ('SA') the AL still has few Properties, many more Methods, but of these methods less than ten are Static.

The SA has many, many Static methods.

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

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

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

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

PHH - 24th. October 2017


Sample one

Well, as long as we take this trial sample as an exercise in learning some finer points of X# syntax, then it is okay.

However, it is not recommended that we do such ugly coded stuff in reality.

There has to be a better way to mix heterogeneous objects in a single collection object.

My issue as seen below is not placing the items (elements) in the 'ArrayList', but knowing what to do

with them when we try to retrieve the data.

Anyway, having been warned, try to study the following :-

[ .. note we can access AL items using index notation ... lines 83, 89, and 95 ..]

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

PHH - 16th. November 2017.


3D (none zero base) example for 'System.Array' class

Explanation of the Project :

Let us start this section with the definition of a possible real life problem which we need to solve.

We will put a business type task as our starting point, and NOT the software, syntax, or even hardware.

Working back from the business 'problem' we will eventually get to the code design and the application

creation. And in this way we will see how we need to specify our 3D array, and its non-zero indexes too.

The Problem :

A marketing survey company has conducted a major survey of customers who have bought a certain

new electric motorcar.

They asked the customers to rate the electric car on only three 'points of interest'. These were the

following :-

- General ease of use during general motoring

- The full driving experience

- Convenience of recharging the battery

Each of these points was marked by the customer on a seven point scale :-

-3 very bad

-2 bad

-1 poor

0 no feelings, OK

+1 reasonable

+2 good

+3 very good

What the marketing company wishes to do is create a 3D grid of these results as points in a 3D space.

For each set of survey results a 'count' has to be added into the results 3D grid.

So for example a single result of { General = -1, Driving = +2, and Convenience = 0 } will cause a one '1' to

be added into the value of the grid point -1,+2,0 and so on for each and every result.

"On a scale of -3 to +3" is how many questionnaires would describe these possible results.

Oh! by the way, we are to be supplied with 13,000 results sets. That is details returned by 13,000 customers.

For our purposes of development and testing we will generate our own random results rather than use the

genuine things !

At the end of data input we will have 13,000 values of '1' added into the 3D grid (array) points. So each array

element will be an integer between 0 and 13,000. However, it is most unlikely that all customer results will

be exactly the same ;-0)

There are 343 points in the 3D space, or lets call it 'grid'. These are likely to sum to somewhere between

15 and 60.

In order to store the input results we need a data structure which is a 3D array in which the index values in

each of the three directions (X,Y, and Z) range from -3 through zero to +3.

Preparation of Test Data :

We need a method or two to allow us to generate some suitable test data.

For each customer we need to generate three integer values between -3 and +3 inclusive. These values

are then returned to be used to locate the 3D array grid point, to which we then add '1'.

Obviously, we then do this 13,000 times over.

Using the Results :

Once we have finished the summation process, we will need to look at all of the 343 grid points as well as

their values. We have been asked to list out the top five and bottom five values with their related index

values.

As well as that the car company wishes to know how many customers rated their experiences

as +3, +3, and +3.

And they also ought to know -3,-3 and -3 in my opinion ;-0)

Right, lets get to work ............

PHH - 22nd. & 27th. November 2017.


The Design of our solution

Okay then folks, we need to briefly discuss the design of our application 'solution'.

We need a single WPF form on which to display our processed survey results. A data grid

would seem suitable for displaying purposes.

A main program will generate sufficient random numbers for our purposes of test analysis.

The main program will use two of my own defined classes, called 'XYZ_GridPoint' and 'MyXIndexArray3D'.

Lets view the project folders that show in the SE (solution explorer) pane :-

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

Yes, it quite a small app. - but we can learn a few things form studying it.

First of all lets see all the namespaces which are required at the top of our .PRG code file :-

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

It would appear form Forum news that line 19 will soon not be necessary as it will be included in all

the X# stuff / files / settings etc.

Lets now take a look at what my work app looks like in the 'main' section.

The following image includes useful test methods which were handy during development - these have

been left in place so that you may study them to see if you can learn anything useful - otherwise they

can be removed.

It is the last four test methods belo which can be commented or removed - check this out thoroughly :-

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

So the commented version of 'main window' is shown below and it still works okay :-

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

Now then, before we inspect the code in the Constructor and Initializer we need to know just what

the structure / definition of our grid class is.

Checkout below the 'XYZ_GridPoint' class :-

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

So one object of type 'XYZ_GridPoint' holds a value as well as the x,y,z coordinates of the point on the 3D grid.

The value actually becomes a summation as the Customer Survey results are processed. Each time we have

a 'hit' on a grid coordinate we add a '1' (one) into the value of the grid point object at that point.

Lets now view the Constructor of the 'main window' - check below :-

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

Notice that in the code shown above, as well as analysing the Customer Survey results, we are

actually timing the whole procedure, from start to finish.

A 'TimeSpan' object is created using the time before and after the survey analysis.

So on line 32 above it would appear is where all the action of analysis kicks off.

Lets check it out - see the details below, first we declare our random number generator, for use for

each and every survey result value.

Then we declare our special 3D array to hold the results grid point objects :-

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

Okay then guys, we are getting somewhere - lines 42 through 44 indicate that there are really only

three methods which do all the work of filling the array grid and reading and analysing the data.

We will follow through in the next section or two, just what happens in these three methods.

See you there ......................

PHH - 25th. & 27th. November 2017.


The 3D array class

We need to realise that it is the existence of the 3D array class that makes this Customer Survey

analysis possible.

The original design of the application was based on having the ability to code a -3 to +3 index range on

all three of the possible indexes X, Y, and Z.

The code of the "MyXIndexArray3D" generic class is shown below in its 'member outline' - check it out :-

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

Please remember that this class is really just a convenient 'wrapper' class, built around the static method

of the System.Array class called "Array.CreateInstance()".

This static method has quite a few overloads and we are using that which a Type with three INT values for

the length of each of the three indexes, X,Y, and Z. Or if you prefer R,C, and S.

The last nomenclature refers to Row, Column, and Sheet - to describe a 3D grid.

As it stands the 3D class can be instantiated with any specified Type - simple data type or more complex

business Type. We will be using a very simple business Type called 'XYZ_3DgridPoint".

How does the "MyXIndexArray3D" generic class work? :

Well basically, there are two methods of the 'wrapper' which act as an interface to the zero based three

dimension .NET array. These store and retrieve objects into and out of the array,

There are also supporting class methods which handle the conversion of the user / developer index range

to the index values required by the zero based 3D array.

We need to handle the translation of index values for each of the three indexes, separately. And also in

both directions - in and out of storage.

Obviously, its a good idea to check that the supplied index value (from our working runtime code) is within

the range it ought to be. We eliminate errors this way - before they happen ;-0)

Here is a suitable checking method - note there are two others, for Column and Sheet :-

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

And now that we have a set of three checking methods we can look at trying to input a value.

In the following 'InVal' method we firstly check to see that the index values are within bounds.

Only then do we try and use the RI, CI, and SI values.

Study the following code carefully, and thoroughly :-

Inputting Values (storing) :

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

Please remember that the above code referring to 'SetValue' is actually part of the .NET Framework.

Here is a screen capture from Help Viewer 2.2 for this class :-

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

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

Outputting Values (retrieving) :

So now we can switch our attention to checking over the 'OutVal' method of mine.

The way we retrieve the stored object from the 3D grid or matrix.

Yes, I know, actually we are getting an object from the array itself - I just like to visualise it as a grid / matrix.

Once again (line 307) we actually use a .NET Framework method, it is just that we wrap around

it some converting of values and some bounds checking - study carefully the following :-

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

Special note should be made of line 307 and the way we use the Generic 'T' to cast the returned object.

If we miss out the cast we get the following compile error :-

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

As you can see from these last two methods of mine, much of the 'huff and puff' of the code is in translating

values for index values, and the checking them before use.

The rest of the XYZ_GridPoint code is not very complex at all, or complicated.

Lets now study the translating methods to see what they do ..............

Translating the Index values - Outside to Inside and the other way :

Although we have three sets of each of these, for Row, Column, and Sheet, we only need to look at and study

those for the Row, as the other two are similar / the same.

Yes, it is a simple as this :-

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

The 'Out' means the index values used by my code, that is outside of the 'wrapper'.

Below we see the method to convert values from inside suitable values to those needed by

my outside code. That is the environment of the app itself. Remember the zero based values

are hidden by the 'wrapper'.

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

And that's about it guys.

Time to move on to the analysis of the survey results.

See you in the next subsection ..................

PHH - 25th. & 27th. November 2017.


The analysis results

Generating the example "Customer Survey" results details :

Okay you guys, since we don't have to hand any real (or genuine) Customer Survey results, we

need to make our own.

We can do this quite easily using a random number generator - this will provide us with sets of

three numeric integer values, each between -3 and +3 inclusive.

First we need to declare a public property fro use across the main window application.

After initiating it we will then be able to use it at any point throughout the application - this is

the way to get random values - one Random class object providing all required values.

Here is how we start - see line 25 below :-

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

So now we need to initialize the declared Random class property - see the next image :-

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

By not providing the Constructor with an input (left empty) we get a non repeatable set of random numbers.

Lets now see how we can use the random number generator to provide a large results set - lets make 13,000

sets of survey values - check this carefully and thoroughly :-

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

In the above image see the code on lines 72 through 74 which basically 'ask' our random number generator

to supply a value of an integer between -3 and +4.

Yes, this seems a bit odd as we don't wish for +4 as a survey result - BUT - the second parameter is exclusive,

whereas the first one is inclusive.

The tip when using random numbers is to check and test thoroughly when you are building up your code., and

definitely check that you do not go outside of your bounds, or index range.

Okay then folks, you need to realise that for our grid use of a bunch of grid point objects of type 'XYZ_GridPoint'

we need to initially fill the whole active range of the 3D grid with new grid point objects with zero in the value

property. The value is zero because no survey results have been supplied yet for any of the coordinate sets.

Here is how we code the initialization :-

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

Please realise that in the above code we do need to have three nested loops for I,J, and K as these coordinate

(index) values need to be stored in the actual grid point object, for use later during the analysis.

It would be no good using an iteration or enumeration as we need a 'hold of' / 'handle on' the X,Y, Z values themselves.

This situation is rather uncommon for many other business solutions, I do realise this ;-0) Usually there is no

connection at all between the objects stored in an array and the index positions of the array 'cells'.

Lets move on ... and see how we increment a grid point object when we have a new set of survey values which

single out (point to) a grid point position, and a stored object - check this out carefully :-

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

You need to think about the above code, as on line 80 we are effectively picking up a 'handle' to an existing

object in memory, and then changing the value of one of its properties.

There is no need to re-assign the object of the incremented property. 'OutVal' provides an object, or even its

'handle' / 'pointer'.

Now then guys, time to actually see dome action.

If we use the method entitled 'FindAllElements' we can bind the results list to a display control

on the WPF form of the 'main window'.

Here is the simple, but effective' code :-

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

And I suppose you would also like to see the details for 'FindAllElements'.

Well, here it is - notice that we use a generic 'List' to make our coding easier. We are

using the fact that a List<T> can 'grow' as we add each new element, and that it is also

very easy to convert a List<T> into an array once we have the list completed.

Study this following image :-

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

Obviously, the array returned is strongly typed to be 'XYZ_GridPoints'.

We could actually re-write the CheckResults() method as shown below - BUT - I like to bind List<T> and

LINQ query results to my WPF controls - its my preference.

Here is the alternative :-

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

Finally, before we move to another subsection, we need to see the XAML script to bring about the data

binding of the results list - check line 11 and the Name property added to line 9.

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

Running the app we first get a display of the time required for execution, and the analysis of the survey :-

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

And after our interesting 'TimeSpan' message box we see the WPF display :-

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

The standard DataGrid control has active sort response in the title bar elements :-

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

So after seeing the smallest values, lets switch our attention to the highest ones :-

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

Yep! all very good, and interesting.

Now its time to move on ..........

In the next sub-section all of the working code is shown for you to copy and use in Visual Studio.

See you there ...........

PHH - 25th. & 27th. November 2017.

Addendum :- The 3,3,3 results and the -3,-3,-3 ones too :-

I almost forgot, we were also charged to find the best and worst results, that is for 3,3,3 and -3,-3,-3.

Here is the code and the answers :-

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

Line 45 is what is extra to the working solution - and here is the code it runs :-

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

So comparing Bad with Good, here are the results for Good :-

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

Please remember, as accurate as these results are, the values are for randomly generated data.

See you in the next subsection ................

PHH - 27th. November 2017.


working code and script

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

<Window x:Class="SurveyAnalysis_WPF.WPFWindow1"

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

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

      Title="Market Survey Analysis :" Height="400" Width="420"

      Background="Beige">

  <Grid>

      <DataGrid Name="dgGridPoints" HorizontalAlignment="Left" Height="294"

                Margin="22,22,0,0" VerticalAlignment="Top" Width="192"

                ItemsSource="{Binding}"/>

  </Grid>

</Window>

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

BEGIN NAMESPACE SurveyAnalysis_WPF

public class XYZ_GridPoint

  public property X AS INT auto

  public property Y AS INT auto

  public property Z AS INT auto

  public property Value AS INT auto

  Constructor(nX as int, nY as int, nZ as int)

      self.X := nX

      self.Y := nY

      self.Z := nZ

      self.Value := 0

  return

end class

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

PUBLIC CLASS MyXIndexArray3D<T>

  property RowIndexLower AS INT auto

  PROPERTY RowIndexUpper AS INT AUTO

  property ColumnIndexLower AS INT auto

  PROPERTY ColumnIndexUpper AS INT AUTO

  property SheetIndexLower AS INT auto

  PROPERTY SheetIndexUpper AS INT AUTO

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

  PROPERTY RowLength AS INT AUTO

  PROPERTY ColumnLength AS INT AUTO

  PROPERTY SheetLength AS INT AUTO

  PROPERTY myArray AS System.Array AUTO

  PROPERTY RowOffSet AS INT AUTO

  PROPERTY ColumnOffSet AS INT AUTO

  PROPERTY SheetOffSet AS INT AUTO

  PROPERTY RowRequired AS INT AUTO

  PROPERTY ColumnRequired AS INT AUTO

  PROPERTY SheetRequired AS INT AUTO

  property Rank as int auto

  PUBLIC CONSTRUCTOR()

      SELF:RowIndexLower := -5

      SELF:RowIndexUpper := +5

      SELF:ColumnIndexLower := -10

      self:ColumnIndexUpper := +10

      SELF:SheetIndexLower := -3

      SELF:SheetIndexUpper := +3

      self:myInitialize()

  RETURN

  PUBLIC CONSTRUCTOR(nRL as int, nRU as int, nCL as int, nCU as int, nSL as int, nSU as int)

      SELF:RowIndexLower := nRL

      SELF:RowIndexUpper := nRU

      SELF:ColumnIndexLower := nCL

      self:ColumnIndexUpper := nCU

      SELF:SheetIndexLower := nSL

      SELF:SheetIndexUpper := nSU

      self:myInitialize()

  RETURN

  PUBLIC METHOD myInitialize() AS VOID

      SELF:RowLength := self.RowIndexUpper - self.RowIndexLower + 1

      SELF:ColumnLength := self.ColumnIndexUpper - self.ColumnIndexLower + 1

      SELF:SheetLength := self.SheetIndexUpper - self.SheetIndexLower + 1

      SELF:myArray := Array.CreateInstance(typeof(T), SELF:RowLength, self.ColumnLength, self.SheetLength)

      //--- will access array for zero base index ---

      SELF.Rank := SELF:myArray.Rank

      //--- make IndexLower from zero ---

      SELF:RowOffSet := SELF:RowIndexLower

      SELF:ColumnOffSet := SELF:ColumnIndexLower

      SELF:SheetOffSet := SELF:SheetIndexLower

      SELF:SheetOffSet := SELF:SheetIndexLower

  RETURN

  PUBLIC METHOD CheckRowIndex(nZ AS INT) AS Boolean

      local lOK := False as Boolean

      IF nZ >= SELF:RowIndexLower && nZ <= SELF:RowIndexUpper

           lOK := True

      endif

  RETURN lOK

  PUBLIC METHOD CheckColumnIndex(nZ AS INT) AS Boolean

      local lOK := False as Boolean

      IF nZ >= SELF:ColumnIndexLower && nZ <= SELF:ColumnIndexUpper

           lOK := True

      endif

  RETURN lOK

  PUBLIC METHOD CheckSheetIndex(nZ AS INT) AS Boolean

      local lOK := False as Boolean

      IF nZ >= SELF:SheetIndexLower && nZ <= SELF:SheetIndexUpper

           lOK := True

      endif

  RETURN lOK

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

  METHOD TransRowIndexOut2In(RowIndexOut as int) AS INT

      LOCAL retIndex AS INT

      //--- do conversion ---

       retIndex := RowIndexOut - RowOffSet

  RETURN retIndex

  METHOD TransColumnIndexOut2In(ColumnIndexOut as int) AS INT

      LOCAL retIndex AS INT

      //--- do conversion ---

       retIndex := ColumnIndexOut - ColumnOffSet

  RETURN retIndex

  METHOD TransSheetIndexOut2In(SheetIndexOut as int) AS INT

      LOCAL retIndex AS INT

      //--- do conversion ---

       retIndex := SheetIndexOut - SheetOffSet

  RETURN retIndex

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

  METHOD TransRowIndexIn2Out(RowIndexIn AS INT) AS INT

      LOCAL retIndex AS INT

      //--- do conversion ---

       retIndex := RowIndexIn + RowOffSet

  RETURN retIndex

  METHOD TransColumnIndexIn2Out(ColumnIndexIn AS INT) AS INT

      LOCAL retIndex AS INT

      //--- do conversion ---

       retIndex := ColumnIndexIn + ColumnOffSet

  RETURN retIndex

  METHOD TransSheetIndexIn2Out(SheetIndexIn AS INT) AS INT

      LOCAL retIndex AS INT

      //--- do conversion ---

       retIndex := SheetIndexIn + SheetOffSet

  RETURN retIndex

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

  PUBLIC METHOD InVal(inVal AS T, RI AS INT, CI as int, SI as int) AS VOID

      LOCAL IMPLIED Val := inVal

      IF CheckRowIndex(RI) && CheckColumnIndex(CI) && CheckSheetIndex(SI)

          //--- apply off-set

           RowRequired := TransRowIndexOut2In(RI)

           ColumnRequired := TransColumnIndexOut2In(CI)

           SheetRequired := TransSheetIndexOut2In(SI)

          //--- set the value ---

           myArray.SetValue(Val, RowRequired, ColumnRequired, SheetRequired)

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

      else

           MessageBox.Show("Either Row, Column, or Sheet is not within bounds!","Outside stated bounds :")

      ENDIF

  RETURN

  PUBLIC METHOD OutVal(RI as int, CI as int, SI as int) AS T

      LOCAL Result AS T

      IF CheckRowIndex(RI) && CheckColumnIndex(CI) && CheckSheetIndex(SI)

          //--- apply off-set

           RowRequired := TransRowIndexOut2In(RI)

           ColumnRequired := TransColumnIndexOut2In(CI)

           SheetRequired := TransSheetIndexOut2In(SI)

          //--- retrieve the value ---

           Result := (T)MyArray.GetValue(RowRequired,ColumnRequired,SheetRequired)

      ENDIF

  RETURN Result

  PUBLIC METHOD FindAllElements() AS T[]

      LOCAL aItems AS T[]

      //--- will return all elements !

      LOCAL aList AS List<T>

       aList := List<T>{}

      FOREACH item AS T IN myArray

          IF item <> NULL

               aList.Add(item)

          endif

      next

       aItems:= aList.ToArray()

  RETURN aItems

  PUBLIC METHOD ViewAllElements() as void

      local cText := "" as string

      LOCAL aItems AS T[]

      //--- will return all elements !

      LOCAL aList AS List<T>

       aList := List<T>{}

      FOREACH item AS T IN myArray

          IF item <> NULL

               cText += item.ToString()+CRLF

          endif

      next

       MessageBox.Show(cText,"Viewing elements :")

  RETURN

END CLASS

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

PUBLIC PARTIAL CLASS WPFWindow1 INHERIT Window

  public property my3Darray as MyXIndexArray3D<XYZ_GridPoint> auto

  public property myR as Random auto

  public property dtStart as DateTime auto

  public property dtFinish as DateTime auto

  public constructor()

       dtStart := DateTime.Now

       InitializeComponent()

       MyInitializer()

       dtFinish := DateTime.Now

      local tsLasted := dtFinish - dtStart as TimeSpan

       MessageBox.Show(tsLasted.ToString(),"Survery analysis took :")

  RETURN

  public method MyInitializer() as void

      self.myR := Random{}

       my3Darray := MyXIndexArray3D<XYZ_GridPoint>{}

      try

            FirstFillGrid()

            ReadInSurveyResults()

            CheckResults()

      catch oEX as SystemException

            MessageBox.Show(oEX.Message,"my Error 'FirstFill' :")

      end try

  return

  public method FirstFillGrid() as void

      local oGPF as XYZ_GridPoint

      For local i := -3 as int upto +3 step +1

          For local j := -3 as int upto +3 step +1

              For local k := -3 as int upto +3 step +1

                   oGPF := MakeNewGridPoint(i,j,k)

                  self.my3Darray.InVal(oGPF,i,j,k)

              next

          next

      next

  return

  public method MakeNewGridPoint(nX as int, nY as int, nZ as int) as XYZ_GridPoint

      local implied oGP := XYZ_GridPoint{nX, nY, nZ}

  return oGP

  public method ReadInSurveyResults() as void

      local x as int

      local y as int

      local z as int

      for local i := 1 as int upto 13000 step +1

           x := myR.Next(-3,+4)

           y := myR.Next(-3,+4)

           z := myR.Next(-3,+4)

           IncrementPoint(x,y,z)

      next

  return

  public method IncrementPoint(nX as int, nY as int, nZ as int) as void

      local implied oGP:= self.my3Darray.OutVal(nX,nY,nZ)

       oGP.Value +=1

  return

  public method CheckResults() as void

      var newList := from item in self.my3Darray.FindAllElements();

                      select item

      self:dgGridPoints.DataContext := newList

  return

  public method TestPoint() as void

      local implied xyzTest := XYZ_GridPoint{-2,0,+2}

      local cText := "" as string

       cText += xyzTest.X.ToString()+CRLF

       cText += xyzTest.Y.ToString()+CRLF

       cText += xyzTest.Z.ToString()+CRLF

       cText += xyzTest.Value.ToString()+CRLF

       MessageBox.Show(cText,"All point details :")

  return

  public method MyTest3D() as void

      local implied m3Da := MyXIndexArray3D<int>{-3,+3,-3,+3,-3,+3}

       MessageBox.Show(m3Da:RowLength.ToString(),"Row index length test :")

       MessageBox.Show(m3Da:ColumnLength.ToString(),"Column index length test :")

       MessageBox.Show(m3Da:SheetLength.ToString(),"Sheet index length test :")

       MessageBox.Show(m3Da.Rank.ToString(),"my Array Rank :")

       MessageBox.Show(m3Da.ToString(),"my Array 3D :")

       m3Da.InVal(13, 1,2,3)

      local nRet := m3Da.OutVal(1,2,3) as int

       MessageBox.Show(nRet.ToString(),"Retrieve value :")

  return

  public method TestMyRandom2() as int

      local implied myR := Random{}

      local implied nR := myR.Next(-3, +4) //--- less than 4 and 'GE' -3 ---

      local nCount := 23 as int //--- just for testing purposes ---

      do while (nCount > 0)

           nCount -= 1

           nR := myR.Next(-3, +4)

           MessageBox.Show(nR.ToString(),"a random integer ..... ")

      end

  return nR

  public method TestMyRandom1() as int

      local implied myR := Random{}

      local implied nR := myR.Next(7) //--- less than 7 and 'GE' 0 ---

      local nCount := 23 as int //--- just for testing purposes ---

      do while (nCount > 0)

           nCount -= 1

           nR := myR.Next(7)

           nR := nR-3   //--- so -3 to +3 generated

           MessageBox.Show(nR.ToString(),"a random integer ..... ")

      end

  return nR

END CLASS

END NAMESPACE

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

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

PHH - 25th. November 2017.


Collections

*** The Secret of using Collections ***

It is of the uttermost importance that you carefully select the right (correct) 'Collection' for the job in hand.

Do NOT choose a collection type because it is familiar to you.

Each collection class has a definite purpose, and this is where you MUST start.

Do NOT reach out for Arrays because you like them. Likewise List<T> objects.

Choose a collection which is designed to do the task required.

Get used to searching and reading the 'Help Viewer 2.2' eNotes - there is a huge amount of good information

in them to help you choose.

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

In order to study 'Collections' we need to become familiar with (a least) two new namespaces. These are

entitled 'System.Collections' and 'System.Collections.Generic'.

The better we understand and regularly use 'Collections' then the better modern .NET programmer

we will become.

PLEASE don't try to use 'Collections' without reaching for the LINQ technology which was built to

process data held in 'Collections'.

LINQ is now ten years old, some would say ten years mature.

So we have something big and powerful in .NET which is ten years old AND still we have guys keeping

clear of it, I can't understand this. Weird I feel !!!

Lets try and put that right in this section on 'Collections', so that we can all become experts and 'lovers of LINQ'.

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

All the Collection classes are zero based for indexing, and this is NOT affected by the \az setting

in the properties of the Project panel, entitled 'Zero Based Arrays', which may be True or False.

The above property is used by the X# compiler for our declared .NET arrays only.

In fact currently, the \az setting is only used to off-set indices for arrays declared with the syntax as shown below :-

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

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

So currently (Nov. 2017) the following syntax makes arrays for us which are ZERO based - check this out :-

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

So when we come to looking into lists with 'List<T>' we will use a zero based index.

Lets move on .................... "remember to go ZERO"

Let us now study the namespace for 'System.Collections'.

Remember that this is older than the 'System.Collections.Generic' namespace and related technologies.

Some History :

'ArrayList' was in .NET Framework 1.1 and List<T> in version 2.0, whereas 'ObservableCollection' was first introduced in version 3.0.

I am getting this information from the details of my 'Help Viewer 2.2' app which is with my VS 2015 and .NET versions 4.5 and 4.6.

Have a study of the following details :-

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

For me, and the type of applications I design and create, ArrayList, Stack and Queue are of most significance from the above list.

Other guys will vary, as their business task will be different.

Now then, lets move on to looking into the Generic form of the 'System.Collections' namespace.

Probably the most important (most regularly used?) of these is the 'List<T>' class :-

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

And now the general look at both the Structures and the Classes - check these out below :-

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

Yes, above we have a whole bunch of 'Enumerator' Structures.

We will study enumerators later in a topic on iteration and enumeration.

Let us now look at the Classes in this Generic namespace :-

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

Above we see quite a few more Classes than we are likely to use, at least on a regular basis.

Oh! and we start to see names including 'Synchronized' which suggests we may be going towards a

world of multi-use, and concurrency, and possibly even thread safe technologies.

Whatever happened to our good old single user single machine app which had sequential code?

You know, the stuff we all liked so much !?

Now we look as though we are even having to go beyond network and multi-user access, the last stuff

with which we had to come to terms.

Okay then, enough of that lamenting, lets see the Interfaces involved with the Generic classes of Collections :-

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

We may even use a few of these Interfaces without even knowing ;-0)

Lets move on, see you in the next sub-section ..................

PHH - 24th. October & 16th. November 2017.


General information

What is a 'Collection' ?

Well the Microsoft eNotes, through Help Viewer 2.2, simply suggest that it is a collection of objects.

We may even describe a collection as a bunch of 'things'.

I would strongly suggest that the programming 'focus' has changed in moving from Arrays to Collections.

To me the focus of the Array approach was the data structure of the numerically organised 'data cells'.

Now the focus for collections are the data objects themselves.

Any focus on the collection type itself is not really a data focus, but more a general behaviour of the

collection type.

For example the Stack<T> and Queue<T> collections are used for how they handle the group of the

data objects contained within then, as opposed to the individual data items themselves.

The modern 'data world' is not really bothered when data objects are stored, or happen to be, just that

we can easily retrieve them when we wish, with meaningful code syntax and queries.

This is where LINQ comes into play.

I would even suggest that 'Enumeration' is a process by which we can step through a group of items

when in fact we don't know (or even care) where they are. The system just gives us a way of looking

at each in turn.

More on this in the next sub-section.

For now, lets just study what Microsoft has to say on the topic of Collections.

I suggest you read the following VERY carefully :-

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

Here is a tip from Microsoft :-

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

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

And some more careful reading :-

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

Okay, probably best to go back and read all that stuff again ;-0)

Once you have a grip of it we can then move on ................

PHH - 26th. October & 16th. November 2017


Iteration and Enumeration - clear as mud !?

I am writing this statement after doing the research talked of below (black type).

In hindsight ?!

Iteration is the process of going through a series of 'things' and processing them in a similar manner,

one after the other.

Enumeration is the process by which we make a whole group or bunch of things into a series, so that

they can be presented sequentially, one after the other. So enumeration is the process of accessing

and presenting.

We have more control of an enumerator, as it can be reset part way through, and we can move backwards

as well as forwards.

So in my books enumeration make iteration possible. So an 'Enumerator' facilitates iteration.

An 'Iterator' would appear to be a simpler form or an Enumerator, where only simple forward reading(?)

of the group is possible.

These are generalised statements to help get our heads around the topic.

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

If you wish to confuse yourself VERY quickly and easily, then do a net search on the following :-

" .net difference between iteration and enumeration c# "

After a lot of cross confusion by 'helpful' contributors to various forums, I settled for this from Wikipedia :-

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

Why does everything in modern IT have to apparently turn out so complicated ?

I think that 'Iteration' can be seen as going through a list (or bunch) of things one at a time and to each

'found' item, usually doing something.

Enumeration seems to be a bit more but similar, in that there are other options to going through the group

of items from start to finish.

With enumeration we can go backwards and also reset to the beginning.

Lets say that FOREACH allows us to Iterate ;-0)

So here is my own simple iterator / iteration :-

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

And now here is a nice simple example of my own enumerator carrying out enumeration.

The iterative process is one where a text string is made from each of the enumerated items.

Check this out thoroughly :-

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

Now that we seem to have a suitable enumerator, lets do something with it - check below :-

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

Okay then guys, lets see what the difference is when we make an enumerator from two

different types of collection.

We will attempt to compare that from the LINQ query result collection, with that from an

array made from the result collection.

Here is the first enumerator :-

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

And here is the second one made from the array :-

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

This seems simpler as well as being understandable.

I feel that I now understand a little more what Iteration and Enumeration is all about.

At least I know enough to be able to apply these ideas in my application code design.

Lets move on a pace or two ..............

PHH - 13th. November 2017.


The 'IList' interface

This is VERY important for 'Collections'.

And it is also the reason why 'Array' can be thought of as a Collection.

An Array implements the IList interface.

Study this carefully :-

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

And now the following :-

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

Now a few useful methods :-

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

Enough for now, we will see and appreciate more on this as we go through eNotes.

Lets move to the next sub-section .............

PHH - 11th. December 2017.


Converting data in one Collection to that in another

For a number of good practical reasons we often need to copy / move / duplicate data from one

Collection object into another, one of a different type.

Or putting it another way, we can make a new Collection of a certain type using the data held in

another and different type Collection.

This is certainly true of the 'ObservableCollection' ('OC') and the underlying List<T> collection type.

In fact this is so commonly required that one of the only three Constructors for 'OC' takes a List<T> input parameter.

Checkout the following :-

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

We can of course make an 'OC' directly from an Array, as seen in the test code following :-

[ .. first we need a suitable array .. ]

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

Our 'ObservableCollection' is strongly typed on 'Car'.

Lets now find and display the data in our newly made 'OC' - see below for details :-

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

You will find the above coding approach very useful generally.

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

PHH - 15th. November 2017.


from Appendix 'X' in ClickStartXSharp

Before I started compiling these eNotes entitled 'ClickStartXSharp' I had been researching

with X# for many months prior to the German conference in Cologne - dated late April 2017.

Because I had included so much work, as well as working code, it seems a sensible idea

to include those eNotes in this fuller, and more complete set of eNotes.

I will not mix them up with newly composed text and exploratory examples, BUT, rather include

the sub-sections as a whole when and where appropriate.

Here is what the Appendix 'X' looks like - the highlighted line is the collapsed folder for the

details I am about to add to this current subsection :-

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

Once the full details are transferred (or rather copied) these eNotes will look like

that shown below :-

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

Okay the guys I will move on to the child section from here and start the detailed transfer.

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

PHH - 19th. December 2017.


Passing collections - returning Collection objects

Okay guys, remember I am coming back to LINQ and my eNotes after a long lay-off ;-0)

This means that in order to write some good eNotes for XSharp (X#) guys (and myself), I first have to return

to my original C# eNotes and see what I managed to do back then.

See, I do use my own notes for myself, and my coding - I always said I did!

Our first simple example shown below (in C# first) is where we make a typed list of a know collection item

type of objects, and return this list.

Here is the old example of mine, we will use 'Invoice' type items later in out test X# sample.

Check out these details thoroughly and fully - line 2182 in particular :-

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

Okay then folks, lets try this for ourselves with X# and 'Invoice' items.

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

In the code above line 450 is responsible for including in our results set 'Invoice items - objects of type 'Invoice'.

This means that we can code line 452 to make a Typed List - yes, of 'Invoice' type objects.

As you can see the conversion in line 453 works just fine.

It won't be this easy when we use the 'Select' clause to create collection items of an anonymous type.

Going a step further ...............

And now just to prove we can return the list made from the results set converted to a List, see below :-

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

Yes, on line 531 we return the typed List, and the compiler is quite happy with this code.

How about us starting a WPF test application (project) and then trying to bind the returned collection to a suitable

control on the WPF form ?

For this exercise we will use items of type 'Person' in place of the above 'Invoice' example use.

Here below is a good start, even if it is not as colourful as my usual WPF efforts ;-0)

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

Well, above the six data rows look to be the six returned data items, of type 'Person'.

Note that the first column is bound to a property which is itself an object, albeit a simple business object.

Name is a property of the Person class which is itself a 'Names' object, with properties of its own.

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

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

In my eNotes on WPF (and last Cologne conference) I have shown how to bind to a drop list, and even bind the

three Names properties to separate and individual columns in the DataGrid shown above.

For now we are doing enough as we should be concentrating our efforts on querying and dealing with returned

data sets in a suitable manner.

Oh! - we had better see how easy it is in the XAML script to show our results data - check below :-

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

Yes, just two line required, 9 and 10.

And to go 'hand-in-hand' with these two lines we need a suitable code line in our X# code too.

Check out line 32 shown in the image below :-

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

Well now, that is all VERY easy I would say.

So what happens when we have some sort of a collection item filled with data items of an anonymous type ?

How do we go about passing such objects around our code structures ?

We need to be careful as we can cause ourselves a lot of unnecessary hassle - problems and issues ;-0((

Originally PHH - 24th. October 2016.

PHH - 19th. December 2017.


The easy way out

Well then guys, the more I research this topic, and the more coding tests I do, the more I am convinced

that this 'easy way out' of things is by far the most sensible approach.

The issue is what 'Type' of collection element (item) do we create with our 'Select' clause in the LINQ

query.

Anonymous Types are all very well and easy to do 'on-the-fly' but then we have to handle the passing of such

results set around our code, and we hit issues of reusing the property values within the results set.

Lets take a clear example to see what I mean.

Below is a simple 'Class' I created to replace the anonymous type (AT) syntax in the 'Select' clause that I have

been using up until now :-

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

Yes, as simple as you like ;-0)

And here below is a commented bit of my previous working code which shows how I used to use ATs in my 'Select's :-

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

So what do we do now that we have a (as Nick Friend calls them) 'concrete' Class ?

Here is what works well at the moment :-

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

In the above code it is line 82 which has changed - in the detail of the 'select' clause.

Notice that at the moment we are using one of the Constructors that I defined within the 'concrete' Class.

I would prefer to use an alternative, something like those commented in the following image - check below :-

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

Lines 83 and 84 show the sort of thing I am after as a tighter syntax.

As it stands at the moment line 82 type of syntax allows us to mistakenly place the right values in the wrong place, or order.

We could easily get things wrong and place 'City' where the 'Email' should go. The compiler would not know better as both are strings.

I am working on it, even without Intellisense in Visual Studio.

Meanwhile, here is the proof that what we have added as 'concrete' Class coding, actually works okay :-

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

*** CARE - Remember ***

If you need to do some serious data binding with WPF then your 'concrete' Class needs to be suitably coded

with facilities to track and notify property changes.

This exercise / procedure can add a lot of code creation to our otherwise simple class.

The good news is that when we come to 'LINQ to SQL' and in particular 'LINQ to Entity' we will usually have

already defined such Classes.

For now let just focus on getting our LINQ query returned data sets in a 'concrete' form which we can then easily

re-use in other parts of our solution code, in other program code structures, so that we can effectively move our

data around with as little fuss and issues as possible.

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

Let us examine a useful approach which once again Robert VD Hulst has helped me with.

*** NOTE *** we will need to provide all four input parameters (or arguments) unless we provide other Constructor overloads.

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

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

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

Here is a suggested 'Better' way to code our Constructor :

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

*** CARE *** we can still go wrong if we make human errors, the compiler is NOT "Magic" !!!

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

Compiler Improvements from Robert and the 'XSharp' Team :

Robert and his Development Team have made some changes to the current compiler (that is at the end

of October 2016) and we will see what these improvements are likely to be for us when we get the next

version of the X# compiler.

Here is what Robert posted a couple of hours ago :-

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

In the above code it was the "Object Initializer" that we were particularly interested in, however, the "Collection

Initializer" is also a nice step forward.

Look out for further samples in these eNotes using the above syntax when the new compiler is available to us.

Originally PHH - 25th. - 28th. October & November 2nd. 2016.

PHH - 19th. December 2017.


Dealing with Anonymoust Type collections

Before we get straight into showing some successful ways to deal with anonymous types, and how

we may pass the results data to be displayed in a WPF form, lets see the sort of output we can easily

generate by the compiler, when we get things wrong - check below :-

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

It is all too easy to generate error messages similar to the one shown above ;-0((

We need to know how to avoid such 'conversion' problems and issues.

Lets see how to launch a working example - see the changes below on line 31 :-

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

So then, what does the function look like that line 31 uses as the data set for 'binding' ?

Below is the simplest way to create a filtered results set with a limited set of data columns - CAREFULLY check the details :-

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

Well, it seems to work just fine.

We can pass back an array of Objects and still the DataGrid displays then as before.

Is there another, and similar, way to pass these back using a typed List ?

One way is to wrap the anonymous type items (objects) as something else, like 'Object'.

Here are some ideas to move us forward - first a bit of a trial with my code :-

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

Now it would appear (see image below) that this works, but we don't want to be enumerating is we can help it.

Here is the launch code and the WPF result :-

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

So if this works OK then what about wrapping up the AT in the query code, and not leaving it until we

have a results collection 'set'?

Here is the next step to wrapping the ATs and passing them on through our code :-

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

Yes, its line 62 which we need to focus upon.

Since we can 'select' any meaningful object (item) that is available within the 'Query' code structure, we can

therefore cast the Anonymous Type (AT) to the basic 'thing' as an object, that is an item of the 'Object' type.

Lines 64 and 65 show how we can then convert the results set collection to a List of Objects.

*** NOTE *** It has taken me a long time to realise that I can do this. We are normally so concerned about

getting the LINQ syntax correct that we tend to forget the rest of the basic .NET and object stuff.

So I have done this in X# before ever doing it in C# - which is nice ;-0))

Now that we can pass back 'anonymous type' collection data, we can access it just about anywhere in our

solution / project code.

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

Getting "Dynamic" !! - a suggestion from 'Robert van der Hulst' :

Well guys, as usual Robert has come good and given me an more than useful suggestion to help me out.

This below is my first go - BUT - there are some definite improvements following.

I did the next bit below, and then went away and slept on the problem, only to come good with a much better

way the very next morning.

Here is what I first did :-

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

Yes, the WPF form shows that the code compiled and worked.

BUT, for my liking I would prefer not to have lines 90 through 93.

We should be trying to remove as many loops and enumerations as possible.

So with that in mind here is what my freshly 'slept' brain came up with :-

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

And YES, line 65 is much neater than our previous rather clumsy coding.

So where does this leave us ?

We have managed to find the syntax return a data results set from both a Function and Method.

What we now need is to find the neatest and simplest syntax to access the property values of the collection elements,

outside of the Function / Method.

Well, the WPF form image of a the screen output, shows us that we can access the property values as far as displaying

via data binding is concerned, but how do we code to get access to these property values?

We need to try some test examples.

As usual Robert (van der Hulst) has provided me with some useful tips regarding the compiler and the required References.

If we try the following code in our solution it seems reasonable to expect it to run ! - check the details fully :-

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

It appears (from Robert) that we are missing an entry in our References section.

Here is what I have when I get the error :-

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

And here is what we have after adding the required entry :-

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

And so when we compile and run we get a 'runner' - yes, it works!

Now we can start to clean and tidy our code - check this out below :-

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

Well, we can certainly get at (access) the properties of the collection elements / items.

Here is the output from the lines 35 and 38 :-

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

And now, most importantly, we wish to know what 'Type' of object are the collection elements.

Lets get and output the items 'Type' - check this out :-

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

Well, yes, that looks reasonable when we know how the returned collection was made in the method 'EightPlusSample()'.

Lets move on a pace or two ............

Let us try and use all of the returned data values, those of the properties of the list items, and display them in a simple fashion.

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

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

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

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

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

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

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

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

Originally was :- PHH - 25th. & 26th. October 2016.

PHH - 19th. December 2017.


The 'Dynamic' Type

Like it or loath it it can work for us in our code.

However, like a lot of things it should ONLY be used in the right place for the right reasons.

Lets first find out a few things about the 'Dynamic' type.

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

And here is some more useful information :-

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

In the following image we see an example of a data query in which I wished to have the results set as a

collection of anonymous type objects - see the commented line 104 below :-

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

When I got error messages I instead resorted to making a special Class called 'CustomerInvoice', and this worked fine.

However, there are times when we may wish not to have to define a special Class, and so we can do as seen in the

next image, that is use the 'Dynamic' type as in my test code.

Here shown below is a case in which I have used a 'Dynamic' type successfully - and for the first time (almost) :-

[ .. the first real time anyhow .. ]

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

So above I can define my anonymous type as I first wanted to, and by declaring it as 'Dynamic' we get

around the compiler warnings / errors and the code compiles and run okay.

Here is how I action the method in my test app :-

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

*** CARE - if you are wishing a object or a collection of objects to be in memory for a while, and used in many

different places, then you may have some difficulty with your code, because of its type.

I still think defining a specific Class type is a simple enough way around the problem - see below

for my current example solution :-

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

Note that you may need to make your Class more comprehensive, depending what you wish to do with it.

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

Originally was :- PHH - 26th. October & 1st. December 2016.

PHH - 19th. December 2017.


List<T>

In today's world (end of 2017) of .NET programming this class is probably the widest used when it comes to holding

data items in memory.

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

A 'List' holds a bunch of Objects and when we use strong typing (which we should at all times possible) all the objects

are of the same defined type.

So we write this class as 'List<T>'.

Generally our list will therefore be called homogeneous, meaning of the same type.

However, because of the underlying rules of Objects, and OOP, we can force a case where we can group together in one

list, objects of different types.

Here is what the Help Viewer notes say :-

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

Since all objects inherit from 'System.Object' then we can use 'Object' as our strong type, as it were ;-0)

So List<Object> can be used for heterogeneous bunches of 'things'.

Here is the inheritance hierarchy :-

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

Lets explore the possibilities of 'List<Object>' just to see if we are getting the gist of what generic collections are all about.

Watch below as we place three VERY different objects into the same single 'List' :-

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

So effectively, the 'List' object is acting like some sort of a container, one into which we can place anything.

This is NOT the best way to use List<T> so don't get the wrong idea.

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

PHH - 24th. October 2017


CARE - with what a 'List' is !

For some guys who are not that familiar with using Help Viewer 2.2 you can search and find the wrong Class for List.

The one we require when working with Collections is 'List<T>' and not just 'List'.

As you can see by the following couple of images, there is another List class in the System.Windows.Documents namespace.

This is NOT what we want, or need, for this topic on Collections - check below :-

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

And here are the Constructor details :-

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

So be aware that there is a List() which is incorrect for what we are after, and only 'List<T>' which is what we need to

create some strongly typed lists.

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

Fingers crossed we make no mistakes.

PHH - 26th. October & 15th. November 2017


Instantiation - samples

List<T> is easy to use and easy to write code to instantiate a new List object.

Lets start simply, by making a list of INTs :-

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

Lets start by seeing how to make a list of exam scores (or marks) within more complex code, below

is part of this code :-

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

This example is from a more complex one as seen in the following image :-

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

*** Note *** line 40 has a second pair of curly braces, the second one takes the properties

and any required data / values.

So what we have above is effectively a List<T> within a List<T>, simply because one property of the object Types

being stored in the List is a List of integer scores (marks).

===================== Other similar but varied examples =====================

'DateTime' :

Lets try some 'DateTime' variations for inputs to List items - check these out :-

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

Zero based index for List<T>, see above code at line 37.

Doubles :

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

The above small piece of code is interesting, and informative. Note the use of 'd' in the curly braces.

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

Decimals :

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

And now notice the use of 'm' for specifying a decimal value in the curly braces.

Lets try something a little different :-

Adding to the Doubles list :

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

Before we can add a new item to the decimal List we have to make a decimal value in the input parameter of the 'Add' method.

Lets play around with strings ...........

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

Strings :

... need for some content here ...

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

It needs to be said, or emphasised that usually in our code we have 'List<T>' where the Type 'T' is

a more complex type than a simple data type like INT or DOUBLE.

In the above example (a way back up the page) our complex business type is 'Student' - here is the Student class definition :-

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

And here is a way to instantiate a student object and add it to a List<Student>.

In fact we add two Student objects to a List which is returned by a STATIC method.

Checkout this code thoroughly / carefully :-

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

So now we can use the data from the STATIC member :-

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

Notice above that the correct property values are displayed.

And now for a bit of syntax learning ................

Variations in code syntax for instantiation :

Check this out below as two different ways to create an empty generic List :-

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

And while we are 'at it' lets see an alternative .NET way to instantiate a Customer object :-

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

I am starting to use 'var' in quite a few convenient place while typing X# code.

Yes, I am starting to like ' var '. Sorry Karl !

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

PHH - 7th. December 2017.


This is possible, but is it wise coding ?

Let us see if we can move away from strong typing and all its benefits - just to see if we

are understanding .NET and the 'List<T>' correctly.

Let us create and define four new classes of object :-

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

Now lets put one of each of these animals in the same 'large' basket. It has to be big because we

need to fit the Elephant in it.

I will leave it up to you to see if this working code is of any real use to us guys learning :-

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

Arrah! I see, we can make a suitable 'Ark' object into which we place the animals, birds, etc..

Ark = Arc :

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

However, in business application logic this is possible not going to be much use.

Lets keep going .........

PHH - 26th. October 2017


Lists to Arrays

Well guys, here is some good news ;-0)

It is a bit easier turning a List into an Array than it is an Array into a List.

This is mainly due to the fact that List<T> has a Framework in-built method to do so.

Here is a snippet from the Microsoft eNotes in Help Viewer 2.2 :-

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

So we can then easily make an array from an existing List<T>.

Check the following code, and in particular line 429 :-

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

This would appear to be easy since an Array is also homogeneous.

But then why can't we go the other way just as easily ?

Why doesn't the System.Array have a 'ToList()' method ?

Well, it doesn't need to really, since we can create a List<T> from an array using the Constructor fro List<T>.

The wrong way :

If you search the web there are few suggestions and possibilities - BUT - in my book none that

are as simple or as straight forward as I would like to see.

Considering what I am about to show you, the web article solutions / suggestions I found were messy, awkward,

and quite unnecessary.

Basically, we can use the Constructor for the List<T> class.

Check this out in the following image :-

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

Okay, shown above we see line 46 directly making a List<int> from an array named 'aNums'.

Lets now see if we can do likewise with a simple business type class.

For the next test / trial we will use the Customer class - checkout below :-

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

And now the second message box detail :-

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

Yes, we seem to have made our array of Customer objects into a List<Customer>.

So what was the fuss all about in the web articles Googled ?

Time to move onwards (and upwards) ;-0)

PHH - 15th. November 2017 & 4th. April 2018.


Lists to ObservableCollections

This is a commonly required task.

An OC can be data bound to a WPF DataGrid control and display immediately changes in the collection of items.

If other parts of the project are causing changes to the items in the collection then these will be shown ASAP.

When we talk of changes we are meaning collection items being added and removed.

The changes in properties of the actuall items are handled differently, and using INotifyPropertyChanged can be

seen in even List<T> objects data bound to WPF controls.

So its not the properties but the collection items, that are what we are requiring when we move from using List<T> to ObservableCollection<T>.

Here are the small number of overloads for the Constructor :-

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

The third one is that which interests us most at the moment.

Let us now try some simple example code of our own.

We can start with a List of STRINGs - check below :-

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

Okay then folks, how does this vary from what type of object we get if we make the OC directly from Strings ?

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

Moving on ...........

Using Arrays as input parameters :

In a similar way to making a List from an existing Array, we can create an OC (ObservableCollection)

from an array - using the overloaded Constructor.

Here is my test sample :-

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

And now the second message box :-

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

So it would appear that many Collections can be created from other Collection objects, using

the Constructor overload seen in operation above.

Nice to know things are so easy ;-0)

Time to move on .............

PHH - 15th. November 2017 & 4th. April 2018.


ObservableCollection<T>

Well folks we have already seen how an ObservableCollection can be constructed, in the

previous sub-section.

We now need to look, read, and study some eNotes details from Microsoft - check this for starters :-

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

And some more Class details :-

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

Here is a search and find for the Constructor :-

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

And now some details of the Constructor :-

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

Yes, we can even use as input parameters a List<T> or an Array (which is always strongly typed).

Keep on reading - OCs are good for WPF and data binding :-

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

*** IMPORTANT ***

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

I have been preaching the above for many years at Conferences and Events !!!

Here is something new to me (not used it yet) :-

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

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

We can read but not touch (i.e. edit) from and through the GUI.

Changes via the underlying code will be shown in the GUI however.

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

PHH - 24th. October 2017 & 4th. April 2018.


Stacks and Queues

You may be familiar with the ideas of Stacks and Queues from earlier programming experience. They

do seem to have been around for ever, but not always available to us in high level languages.

Simply a Stack operates by objects being placed on the Stack and the operational rule is 'Last in

First Out'. So the first object stored has to wait till last (or the end) to be removed, or accessed.

On the other hand the Queue operates on the principle of 'First in First Out', or to put it another way,

'Last in Last Out'.

I suppose we can think of them as rather specialised lists.

Check this detail :-

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

And now this detail :-

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

Lets move on to more modern stuff :-

Here we enter the world of Generics :-

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

And some more detail :-

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

As Microsoft always recommend - use the <T> generic form if you have a choice.

Moving on ...................

PHH - 24th. October 2017 & 4th. April 2018.


LINQ - a big part of Collection use

Language Integrated Querying :

For those guys out there that think LINQ is some of Microsoft's new technology, be aware that

it is actually ten years old - it came to the public with VS 2008 it would seem. And 2018 is only

a few weeks away.

It would appear to me that more guys have not embraced LINQ for the same reasons they have

not come to terms with SQL Server, or any SQL dialect.

I personally am glad that I made the big effort to get into using both technologies. Its worth the

effort in my opinion. (SQL and LINQ)

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

The modern 'way' to get the most / best from both 'Collections' and 'Arrays' is to use LINQ code.

Since I discovered how to use LINQ in my code I have found it most helpful indeed.

I now look to use LINQ querying wherever I can in my solutions. Code is much more readable.

It makes life so much easier, and also my code much clearer to read and easier to maintain.

Using LINQ I can dispense with quite a lot of old fashioned FOR/NEXT loops.

It would appear that LINQ was created and designed to work with Collections - it just also

happens to work well with Arrays too.

When we effectively use LINQ we are not bothered by where items are in the 'list' / 'group' in

a similar way to us not knowing where SQL data is actually stored.

LINQ is much easier to use if the developer has made (or started to make) the move over from

DBF to SQL.

Some guys would say that LINQ is SQL in memory. Well, what they should be saying is that

LINQ syntax is a relational type of syntax, and not 'positional'. So place / position of data does

not matter, just what it is.

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

Now check this information out carefully :-

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

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

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

We will see many good and working samples of LINQ as wee study these eNotes.

Lets move on and learn !

PHH - 15th. November 2017 & 4th. April 2018.


What a serious user has to say - Nick Friend

Nick Friend has been a member of the VO/Vulcan/XSharp community for many a year, and longer than me.

He does some 'real' serious work on large applications for the South American construction industry.

Although he now codes in C# he has a lot to offer us with his .NET knowledge and practical advice.

Here is what he posted in the XSharp forum a while back - I think it was end January 2017.

==========================================================================================================================

======================================================================================================================

======================================================================================================================

I will use this sort of data of my own to explore the X# code syntax required to achieve what Nick has shown us.

Lets roll up our sleeves and get cracking ............

Moving on to learn more .............

PHH - 30th. November 2017 & 4th. April 2018.


An example involving Lambda and Index

Get one thing straight - it is not normal or common to involve the index - but this works, and we

do need to know about it.

======================================================================

Let us now examine in detail a small working example of LINQ 'method syntax' to form a query.

The interesting thing is that we need to use 'method syntax' because the 'where' clause can't be

formulated (or expressed) in any way which involves the 'index'.

Study this code syntax below :-

===============================================================================================================================

And here is a simpler version of the same sort of query :-

===========================================================================

The best we can do the 'query syntax' way is shown in the following image :-

=================================================================================================================

Well, it seems to give us the correct results ;-0)

Now then, lets discuss something related to the 'Index' values that we seem to have available to us in our Lambda Expressions.

At first glance the Index values do not seem consistent - we need to investigate.

"Which 'Index' for what !?" :

Let us first do a simple test or experiment.

Checkout below what we can find out or glean from this code :-

==============================================================================================================================

Thomas seems to be the first data element but in the results Henry has an index of '0'.

And so to get a fuller view of what is happening lets change the results set a little (bit) :-

==================================================================================================================

Arrrh! - it would seem that our ' i ' values are index values on the results sets, and not the original

data set.

AND - that the index therefore changes for items selected, and depending on what the selection criteria are.

So to summarise we must not think of then as index values for array elements.

Personally, I would suggest that you keep well away from indexes and do things a more modern way - embrace 100%

the Collection approach and using LINQ.

Lets move on ............

PHH - 8th. December 2017 & 4th. April 2018.


Anonymous Types

We need to have anonymous types when using LINQ. There really is no option.

It is possible to try and avoid them - BUT - we are making unnecessary work for ourselves if we do.

Quite simply an 'AT' or Anonymous Type' is a new class created / defined "on the fly" as we say. Meaning

just as we go, at the time we need it. We the coder / developer guys do not give it a name - the system

does that automatically for us.

In X# syntax we use .... " CLASS { a, b, c, ........ } "

The parameters possible range is from one to many, and each one is a Collection item, or item property.

Lets look at some working code to get an idea of what we are discussing - checkout line 95 and 96 below :-

==================================================================================================================

In the above example our anonymous 'class' or Type has two properties.

We can add to this, or change it, by adding a third property, as seen below on line 96 :-

======================================================================================================================

So 'p' is the object of the AT.

The rules for making our 'AT' seem to be slightly different to when we use an existing class (Type)

where we seem able to use values from Methods etc..

We need to explore the above, so we can compare known and declared Types with those that are Anonymous.

Lets move on to a related topic / issue ...........

Gaining access to the property values in an 'AT' :

Once we have data from our LINQ query in a Collection of 'AT's we need to know how to access

the items / elements of this new Collection.

Quite honestly, the easy way is to get the system to supplied the detail / information.

Check this out below :-

=====================================================

The way to get around the problem of knowing what type of object the items in the 'TestIndex' Collection are,

is to use 'implied', as seen on line 99 above.

If you wish to blow your mind then lets ask what the type is in the following way - check below :-

======================================================================

Yeh! - it looks messy, long, unusable, but not really complicated or rocket science.

Best to keep such things hidden by using a few smart tricks in our code.

Lets move forward ..............

What is NOT allowed - 'ATS' :

===============================================================================================================================================

And another - showing an attempt at a calculated field :-

======================================================================================================================================================

We need to explore in more detail .............

What 'IS' allowed - 'ATS' :

Checkout the following :-

======================================================================

And this improved (or corrected) syntax, shows how to code our calculated field :-

===================================================================================================================

Now we have 'square', we can also make 'half' - see below :-

================================================================================

So currently our AT has five properties called :- " s, i, Length, square, and half ".

And if we keep using this syntax we can then use methods to change the data values.

Check this out below :-

====================================================================================

Yes, this feels as though we are getting to grips with AT's.

So now that we are getting progress, lets find a clear and tidy way to display our chosen syntax :-

=================================================================================

And even tidier below, if that is what you like ............

Tidying up the code !? - if this is your 'thing' :

=============================================================================

Well, we have certainly made progress. Time to move on.

See you in the next sub-section ...........

PHH - 8th. December 2017 & 4th. April 2018.


Blocking and Concurrency

I was asked to cover this topic by Meinhard, for the Cologne conference in April of 2018.

In fact it is just another, but specialised, Collection.

Read the details below to see what it does, and what it offers the developer / coder.

==============================================================================================

So we need this Collection type if we have multiple places in our code where data in a Collection is likely

to be accessed at the same time.

Don't use another and different Collection because you are familiar with it, choose a Collection on its merit.

Choose a Collection for what it does, what it has to offer.

Lets read on :-

===============================================================================================================================

For many developers it may be a bit early to require such a Class - BUT - it is wise to know what is available, just for

that special time when we need it.

Finally check this out to :-

============================================================================================================================

Lets continue and find out more about 'Blocking' in the next subsection ........

PHH - 10th. November 2017 & 4th. April 2018.


Blocking

Here is another specialised Collection Class - lets read and see what it has to offer us :-

=========================================================================================================================

And there is a generic 'flavour' of this Class :-

=====================================================================================================================

I will leave it up to you to decide when you need to reach (use) for this Class.

See you in the next sub-section .................

PHH - 16th. November 2017 & 4th. April 2018.


Immutable

============================================================================================================================================

====================================================================================================================================

==================================================================================================

============================================================================================

PHH - 16th. November 2017 & 4th. April 2018.


Navigation: »No topics above this level«

Return to chapter overview

Interfaces

PHH - April 11th. 2017


Why do we need Interfaces in .NET ?

PHH - April 11th. 2017


What are Interfaces in .NET ?

PHH - April 11th. 2017


Navigation: Interfaces >

Return to chapter overview

When to use them

PHH - April 11th. 2017


Navigation: Interfaces >

Return to chapter overview

How to use them

PHH - April 11th. 2017


When and how to create our own

PHH - April 11th. 2017


Delegates

I like to think of Delegates as a way to run (or execute) an 'Action' method, whenever we require it.

(Or indeed a few - more on this later.)

The method to be 'actioned' (or run) can be either a STATIC one or one belonging to a Class

of an instantiated object (an instantiated method).

So effectively, using the Delegate system, we can run a chosen method whenever we see fit,

from anywhere in our code.

From the 'Delegate' (when we run it) we are said to 'Invoke' the method, and we can in fact action (or Invoke) more

than one method from the same Delegate, as long as we instantiate it multiple times.

I suppose that translates to 'one action method per instantiated 'Delegate'.

BUT - we can concatenate Delegates it would seem.

HOWEVER, after doing things this simple / easy way, we will see how we can in fact create a list

of methods to be invoked from one Delegate. It seems that one Delegate can in fact be many !

More on this concatenation later - first some simpler stuff.

Like with lots of .NET topics, we first need to see, and get to grips with, the syntax required by X#,

then later after getting some working results, we can worry about 'why' we should use Delegates, and even

how to use then best, or most wisely.

At this point it is probably a good idea to show a simple example.

[ .. lets start with the simple example in the Help Viewer eNotes .. ]

First the overall 'WPF' Class, the outline :-

============================================================================================================

Let us now expand the above 'collapsed' initialising method - check these details :-

=======================================================================================================================

Note above in lines 33 and 34 we use the same Delegate 'signature' to create local objects.

The two methods used as input parameters are defined within the 'mySampleClass' Class code.

The instance method first needs a object to be instantiated, as on line 32.

The STATIC method is directly accessible as we would expect.

Study carefully the following details of the two methods :-

=================================================================================

And finally we need to see the output when the code is compiled and run.

You can compare this with that shown at the bottom of the Help Viewer page :-

========================================

And to compare, here is the output display from Help Viewer 2.2 :-

===============================================================

Okay, I forgot to correctly handle the output of an extra ' ; ' in the text part of the string.

I will leave it up to you to put this right ;-0)

PHH - 25th. December 2017.


the Console version of the simple example ...

==========================================================

============================================================================================================================

============================================================================================================

PHH - 27th. December 2017.


working code

USING System

USING System.Collections.Generic

USING System.Linq

using System.Text

begin namespace ConSamplesDelegateXS

  //--- Declares a delegate for a method that takes in an int and returns a String.

  public delegate myMethodDelegate(myInt as int ) as string

  public class SamplesDelegate

      //--- Defines some methods to which the delegate can point.

      public class mySampleClass

          //--- Defines an instance method ---

          public method myStringMethod(myInt as int ) as string

              if ( myInt > 0 )

                  return( "positive" )

              endif

              if ( myInt < 0 )

                  return( "negative" )

              endif

          return "zero"

          //============================

          //--- Defines a static method ---

          public static method mySignMethod(myInt as int) as String

              if ( myInt > 0 )

                  return( "+" )

              endif

              if ( myInt < 0 )

                  return( "-" )

              endif

          return ( "" )

          //============================

      end class

  end class

  FUNCTION Start() AS VOID

       Console.WriteLine("Hello World!")

       Console.WriteLine("Press any key to continue...")

       Console.ReadKey()

      //-----------------------------------------------

      local implied mySC := SamplesDelegate.mySampleClass{}

      local implied myD1 := myMethodDelegate{ mySC.myStringMethod }

      local implied myD2 := myMethodDelegate{ SamplesDelegate.mySampleClass.mySignMethod }

      // Invokes the delegates.

       Console.Clear()

       Console.WriteLine("lets start a fresh ...")

       Console.WriteLine(e" {0} is {1}; use the sign \"{2}\" .", 5, myD1(5), myD2(5))

       Console.WriteLine(e" {0} is {1}; use the sign \"{2}\" .", -3, myD1(5), myD2(-3))

       Console.WriteLine(e" {0} is {1}; use the sign \"{2}\" .", 5, myD1(0), myD2(0))

       Console.ReadKey()

  return

END NAMESPACE

PHH - 27th. December 2017.


The Delegate Type / Class

======================================================================================================================

=======================================================================================================================================

====================================================================================================================

PHH - 27th. December 2017.


Invocation lists ...

===========================================================================================

======================================================================================================================================

===========================================================================================================

================================================================================================

It would seem that we may have some confusion to clear up regarding 'Multicast' delegates.

================================================================================================================

PHH - 27th. December 2017.


Adding / Removing delegates ...

=============================================================================================================

=================================================================================================================

================================================================================================================

PHH - 27th. December 2017.


Explained by Chris Pyrgas ....

It seemed too good to miss so I have added this before asking Chris for his permission - just in case I forgot

and/or the details got lost in the Forum posts.

Hi Karl,

You use delegates when you need to call some code that is not known at compile time. For example you are creating a library with a function that does very heavy calculations and takes a lot of time to complete. Because of that, from time to time you want your library to send back some data (to the app that uses it) about its progress and maybe let the calling app decide if it wants to cancel the operation (random example that comes to mind, a windows update that takes too long time to complete and makes your system crawl and you cannot work, so you are MS and want to give some reliable info back to the user about the progress and allow him to cancel the %^%&#@ update).

This can be nicely done with a delegate. You can define one as:

DELEGATE LongProcessProgressReportDelegate(oInfo AS ProgressInfo) AS LOGIC

and then simply use (simplified code)

CLASS ToolInLibrary

EXPORT oProgreesDel AS LongProcessProgressReportDelegate

METHOD HeavyCalculations() AS VOID

// ...

IF MuchTimePassedSoMustReportBack

LOCAL somedata AS ProgressInfo

// ...

somedata:TimeRemaining := 17 years

IF .not. SELF:oProgreesDel:Invoke(somedata)

// cancel operation

ELSE

ENDIF

Your library code knows nothing about the actual code that will be actually called at runtime, it only knows (and enforces) that the method that will be called at runtime must and will have those specific parameters and return type that you specified in the delegate.

The calling code, the one which uses your library, would then need to implement a method with that signature (you know what I mean ):

CLASS TheRealApp

METHOD DoSomeWork() AS VOID

LOCAL oTool AS ToolInLibrary

// ....

oTool:oProgreesDel := ProgressCallbackMethod

oTool:Heavycalculation()

// wait and wait and wait...

RETURN

METHOD ProgressCallbackMethod(oInfo AS ProgressInfo) AS LOGIC

SELF:DisplayProgressInfoToUser(oInfo)

IF UserIsCompletelyAngryWithDelay()

RETURN FALSE

ENDIF

RETURN TRUE

END CLASS

This will work nicely at runtime, your library will be giving updates to the main app in a type safe and fast manner. Instead of invoking a delegate, you could possibly use just Send() to call a method in the calling code, we had that in VO for decades you will say. Yes, but this is not type safe, there is no parameter and return type cheeking at compile time, so you can accidentally easily use wrong params in the ProgressCallbackMethod() callback, which will throw an error at runtime only. With the delegate system, all is checked at compile time and the calls are guaranteed to be made correctly.

Another way to implement the above scenario, would have been to declare an interface IProgressReport with a method member named ProgressCallbackMethod() and require the calling code to implement this interface, but this is probably overkill, to request such a thing just for a single callback method.

Btw, .Net has brought this concept a step further, by introducing EVENTs, which are basically a warper around delegates, but their mechanism is the same and they require the exact same delegate type. In the sample above, you can replace the line

EXPORT oProgreesDel AS LongProcessProgressReportDelegate

with

EXPORT EVENT ProgreesDel AS LongProcessProgressReportDelegate

and it is the same thing more or less. So usually you are using delegates with events (like all those events in the Form class, which all use the "EventHandler" DELEGATE), but there are cases where you still need to use a delegate in its simple form, specified in a LOCAL or class var, for example when needing to provide a callback to a Win32 API function. A delegate instantiated and holded in a LOCAL also has the advantage that it can be passed around in your code just like any other variable and instantiated by various places in your code. But admittedly, there are very few cases where you would really need to do that.

Anyway, again this post got a lot larger than planned, hope it clarifies things about delegates a bit, instead of making them look even weirder

Chris

XSharp Development Team

chris(at)xsharp.eu

PHH - 20th. February 2018.


Generics

========================================================================================================================================

=================================================================

PHH - April 11th. 2017


What are Generics ?

Lets have a look to see what is written in the help notes of Help Viewer 2.2 from my VS 2015 install.

============================================================================================================================

also expressed in the MS notes was :-

Defining and Using Generics

Generics are classes, structures, interfaces, and methods that have place-holders (type parameters) for one or more of the types

that they store or use.

A generic collection class might use a type parameter as a place-holder for the type of objects that it stores; the type parameters

appear as the types of its fields and the parameter types of its methods.

A generic method might use its type parameter as the type of its return value or as the type of one of its formal parameters.

PHH - April 11th. 2017


Terminology - Generics

Well guys, here it is, the words of the Gospel on Generics according to Microsoft.

I think they could have worked on this more, to make it a bit clearer and understandable ;-0)

==========================================================================================================================

PHH - 24th. October 2017


How to use existing Generic types

This is how many/most of us will use Generics, certainly to start with.

So lets see how we can get the most from what is already easily available to us in the .NET

framework.

PHH - April 11th. 2017


How to use Generics in our own business classes

Okay then guys, this is indeed an interesting, and useful, section to study and copy.

Moving to using Generics in our our defined classes is one of those 'step-ups' which makes

us a better programmer / designer.

It is one thing to just use Generic types, but it quite another thing to create our own types

based on Generics.

At the beginning it took me a little while to get my head around Generics, but I can tell you that

a good example does then make it easier for us to follow - copy and adapt - to make our own new

class types based on '<T>'.

Luckily I have a very good recent example of my own to share.

PHH - 24th. November 2017.


Generic Collections in the .NET Framework

============================================================================================================================

==========================================================================================================================

Classes :-

========================================================================================================================

========================================================================================================================================================================================

PHH - 24th. October 2017


How to create our own Generic types

============================================================================================================================

PHH - April 11th. 2017


Other Generic Types - 'Nullable'

=============================================================================================================================

=========================================================

============================================================================================================================

============================================================================================================================

=========================================================================================================================

==========================================================================================================================

PHH - 24th. October 2017


Navigation: Generics > Other Generic Types - 'Nullable' >

Return to chapter overview

Some X# examples

PHH - 24th. October 2017


Getting better performance with Collections

PHH - April 11th. 2017


from "Appendix 'X' " of 'ClickStartLINQ'

was called :- Generics - getting started in X# :

This is useful, even though it is in C# syntax :-

https://www.tutorialspoint.com/csharp/csharp_generics.htm

==============================================================================

Okay guys, lets make this topic as easy as possible. It is NOT difficult if tackled right.

What technology Microsoft builds for us is usually much simpler than a lot of authors make it sound / seem.

Many eBook articles, and web articles, get us lost in the details of doing stuff, and forget to make the overall

pattern of things understandable. We need to see the bigger picture.

Also poor examples can just muddy the waters.

We need to know what Generics is, and what the technology can do for us. Then find a few good

and straight forward examples.

We also need to draw some useful and appropriate parallels with what we already know and use.

Eventually, we will be able to design and create our own generic classes (types), methods, interfaces,

events, and delegates. There is a lot in common for each of these, so its not as bad as you think.

Lets get our sleeves rolled up and make a start ;-0)

===========================================================================

What is Generics ? - a simple answer :

The technology of Generics allows us to write .NET code such that class 'Types' are supplied at runtime

and not hard coded.

So we can write methods (and other members) which have code which refers to a general 'Type' and the

Type is provided at runtime in a similar way to the values of variables we use as parameters to methods

and procedures.

By supplying runtime 'Types' we can reuse code over, and over - for different Types. In a similar way to

the way using methods and parameters saves us lots of lines of code when variable input parameters get

different values assigned during code execution.

So for a while we all have had 'data item passing', and now with Generics we have 'Type passing'.

We must.nt think that Generics is much more than that, even though its impact can be great.

Lets look at some general X# syntax for before and after Generics are used.

*** NOTE *** It has been hard for me to find in my current coding, a suitable sample without Generics

being involved somewhere in the code syntax !!!

Here is one of the few old style methods I could find :-

===========================================================================================

Notice above that we have data parameters being passed in and through the method code - in at

line 163 and then out to other methods in lines like 166 and 174.

Here below is a simple 'Initialise' method I wrote for a demo app for Cologne 2017, we now see on line

60 the use of a Generic type provided by the framework. Always look for ' < > '.

So on line 60 the Type(s) are placed between the ' < > ' and the data item parameters between the ' ( ) ' :-

====================================================================================================

Out of interest, while looking for some standard non-Generic code syntax, I found it very difficult to find any.

I really had no idea that I had come to use Generic types with my MVVM pattern of design. Its everywhere!

This probably means that I am doing something right ;-0)

In-built Generics within the .NET framework :

In the past with .NET, before Generics was introduced, the performance of using collections was degraded,

or compromised, because collection items needed to be cast and recast as items in a collection were accessed.

We now have available to us in current .NET frameworks the 'System.Collections.Generic' namespace, which

exists in the assembly named 'System'.

Lets look at System with the Object Browser in Visual Studio 2017 :-

==========================================

And now if we expand the tree further we see that the 'Generic' items comes

to this shown below :-

=====================================================================

Do NOT panic, some of this will become more understandable as we progress through

these learning eNotes.

Lets first of all see if we can use a very commonly used class type from within the Generic namespace.

I use ' List<T> ' a great deal when I use LINQ to process my data in collections.

My use is likely to be ' List<int> ', 'List<string>', or more likely ' List<Customer> ', when doing business code logic.

Lets now study some code .............. and suitable examples ............

It is very easy to USE the Generic types in code, but a little more work to CREATE our own Generic types,

or classes. Remember that Types and Classes are the same thing.

In my search for some suitable X# code syntax I found the following business data 'Model' class, part of

the 'Stock System' app I wrote for Cologne. Check below to see just how many lines have the Generic

'sharp pointy' brackets (or chevrons) :-

=====================================================================================================

The number count is just about 50 / 50 for lines that are related to generics.

What is going to be the hardest part of using Generics ? :

This seems as if it may be a complicated question, or even one impossible to answer.

Well, I think the answer is simple - it is knowing when to apply our own Generic code for our own

business classes and methods.

There are quite a few simple samples around, which unfortunately are not much use in guiding us as

to how and when to do Generics for our business app logic.

For myself, as I see things at the moment, the BIG step for anyone is to start using the easy to access

Generics from the framework, and apply it to each and every collection we create and use.

Then as we gain more experience we will also see some better examples from others, and then off

we shall go ;-0)

Be careful that you do not invent the wheel with Generics, and do your own coding when in fact stuff

already is there in the framework, for us to use right off the shelf.

Originally done :- PHH - 22nd March 2017.

PHH - 20th. December 2017.


Information in MSDN help notes

Here are a few useful snippets from the Generics help details - read them to get our brain

thinking the right way before we try and make some of our own Classes and Methods.

===============================================================================================================

===============================================================================================================

=================================================================================================================

==============================================================================================================

==================================================================================================================

=========================================================================================

=================================================================================================================

=================================================================================================================

===========================================================================================================

*** NOTE *** I am not sure I quite understand the above note !? I will work and think on this.

================================================================================================

Originally done :- PHH - 23rd. March 2017.

PHH - 20th. December 2017.


Some simple basic rules and thoughts

When coding our own Generic members - classes, methods, interfaces etc., takes these

rules of thumb into account :-

- Although many samples show just one 'T' or Type in the chevrons ' < > ' we can in fact have many.

Here is a working example with two :-

================================================================

- Although it is common to use 'T' for a type, or something similar 'T2', 'TVal' etc., it

is only convention. We can use anything as the textual 'place-holder'.

Here is one I did recently :-

Whatever we do use, we must then follow it through with all the code, obviously !

So we see this in the Constructor as well :-

=============================================================================================

- Although I have not seen any examples showing 'Overloading' we can do it successfully, just

in the same way as we do for parameters in our methods and classes, etc..

The following overloading compiles and runs :-

=========================================================================================================

The only real difference for Classes is that the signature is in the class definition and not the Constructor, as

it is when we overload using parameter signatures.

Here is an example Method definition, with types and data parameters together in the signature definition line :-

=====================================================================================================================================

- As with parameter overloading, we can't have two definitions with the same signature.

For Generics that simply means the number of Types. Since the text used in the Types placed in the signature,

then it is ONLY the number of types which counts.

======================================================================================================

=========================================================================================================================

====================================================================================================================================================================

=======================================================================================================================

- Although it may be a little early in our studying of Generics, we can 'Nest' generic types.

Or, more correctly say 'wrap' them - we shall see when we try some code .....

By nest / wrap, I mean place an already defined type within the definition / specification of another - hard to get your head

around this, then look at an example :-

======================================================================================================

The above compiles and so we have the correct syntax.

Lets try and add some code to allow us to run the test method, as well as output some simple

message text to show we have actually visited and run the code :-

=================================================================================================

And now the updated code and the runtime 'proof ' :-

=====================================================================================================================================

Okay guys, it can appear confusing to us who are new to Generics, but it is not either magic nor rocket science.

We just need some experience and practise at coding such stuff.

We also just need to remember that the 'T' or whatever text we use is just a 'placeholder' until runtime.

We also need to remember that in multiple Type specifications, we can often have duplicate Types. By this I mean

if in input parameter terms we can have :-

========================================================================================================================

Yes, above we have three input parameters, each of data type 'string'. Not unusual, so why not :-

==============================================================================================================================================

================================================================================================================================================

CARE >>> it seems to me to feel wrong if we know at time of coding what the Type maybe.

The whole point of Generics is to code things NOT knowing what the type will be.

I can see that some newbies to Generics may try to use it when in fact they don't require it !!!

Obviously, we have to use the correct and suitable syntax within the body of our Generic method.

This is where we need to stop trying to explain and where good and meaningful examples take over.

I will ask the community for their ideas through the X# Forum.

For now here is another example to thing about - it compiles okay :-

============================================================================================================================

Notice how we can use the Types within the list of parameter definitions.

Lets try a simple test, check these details :-

========================================================================================================================================================

And the calling code is as shown below :-

====================================================

We can now look at some cases where we get the coding wrong, simply because we anticipate one way we

may use the Generic member.

With the above test code in mind, lets try this code in our method :-

=========================================================================================================================================

Yes, we are not allowed to cast with Types that are undetermined at compile time.

And here is another case which makes sense that it is not allowed :-

============================================================================================================================================

I think what we all should do is go and carefully examine the Generic types already defined for us in the

new frameworks provided in the latest .NET version.

I would hazard a guess that Microsoft have done most of the work for us.

Lets start by maximising the performance of our Collections by using stuff like 'List<Customer>()' in place of 'List()'.

So to me the BIG question is not what can we do with Generics BUT what should we be doing in our business solutions with Generics.

Lets move on to another sub-section .........................

Originally done :- PHH - 23rd. March 2017.

PHH - 20th. December 2017.


LINQ - processing Collections

*** NOTE *** I suggest you refer to the 'Collections' main section to see LINQ in action.

==================================================================

Here is a good link from a Microsoft publication source :-

https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/linq/index

And here is an important part of this link above- read it through carefully :-

======================================================================================================================

It is important to read through each of the bullet points above.

"As a rule when you write LINQ queries, we recommend that you use query syntax whenever possible

and method syntax whenever necessary.

There is no semantic or performance difference between the two different forms.

Query expressions are often more readable than equivalent expressions written in method syntax."

PHH - 7th. November 2017.


Removing some confusion in code between C# and X#

Okay guys, lets be reasonable, even if we intend to code our .NET apps using X# syntax we can't

ignore seeing helpful snippets written in C# on the internet.

So we MUST be able to remove any confusion when it comes to C# vs X#.

In the case in hand it is very easy, or simple, to sort out - once we know the problem !

It is that using LINQ code we see semi colons, and in both languages syntax, BUT, they mean

totally different things.

Let us look at a simple C# LINQ query syntax - check below :-

========================================================================

Notice that in the above C# there is only one semi colon ' ; ' and that is on the last line.

In fact that semi colon signifies the end of the code statement which starts all the way back

with ' var '. All other CRLF type characters are ignored, so the code statement runs across four lines.

Now then folks, look at the similar X# code syntax and spot the semi colons :-

=====================================================================================================

Yes, we have have three and just in four lines ! But NOT at the very end.

This is simply because in XBase (or X#) the semi colon character signifies a line continuation.

So the four lines of code are in fact one single code 'statement'.

This means that the only place in our X# LINQ query statement that we don't get a ' ; ' is at the very end, quite

unlike C# code.

So now you are warned ;-0)

Tread carefully ............

PHH - 1st December 2017.


'Query Syntax" - Microsoft's 'Sugar' syntax

For some reason I don't understand, so developer guys wish to denigrate the what is called 'Query Syntax',

or 'Query Expression Syntax'.

Let's see two equivalent LINQ queries - each with different syntax, the first is the 'sugar' variety :-

=======================================================================================================================

It is realised however (by most guys) that 'Query Syntax' is code which is more easy to read.

====================================================================================================================

We do need to fully appreciate however, that all queries can be written in 'Extension Method' syntax, but only some queries

in 'Query Syntax'.

So we do need to build up and use at least some skills and familiarity with the 'Method' approach.

Take this on board as well :-

=====================================================================================================================

Moving on a step or two ..........

Here is the X# equivalent working code. for the 'method syntax' :-

====================================================================================================

And now the working LINQ code for the 'Query Syntax' :-

=================================================================================================================================

Just to remind you (if you already knew) that 'CLASS' then a pair of curly braces ' { } ' means an Anonymous Type.

So each of the elements in the returned 'result' list / collection is of an anonymous type made by the system.

This is just because here (above) we don't have a convenient class to take FirstName, LastName, and Address.

For much more detail on using Extension Method syntax in LINQ queries see the next subsection.

More detail on using 'Query Syntax' - or the 'sugar' syntax of LINQ :

PHH - 30th. November 2017.


LINQ - define query, execute query, use results collection

Okay folks, something else IMPORTANT that we need to get straight about LINQ.

We can write code to define a LINQ query, but until we encounter another code statement

which uses the data form a query, the actual query itself is not run, or executed.

On the other hand, every time we use the defined query in a code statement it is 're-run'.

This can have performance implications, particularly when it comes to LINQ with SQL and

also Entities (Entity Framework 6).

This sub-topic is called "Deferred Query Evaluation".

It feels like a good time to do some coding and experimentation ..........

Let us use the Students in a class set (note class is for the 'group' of people and NOT objects!).

Here is some simple code as a first example :-

=================================================================================================

We will look at the small group (four) of students after we check the WPF script for the 'DataGrid' and data display.

Here is the script which produces the above data output :-

======================================================================================================

And now we need to see the complete group of student records, before we do the filtering with the Where clause.

Check these four individual student details :-

==================================================================================================================

So we can see easily that 'Terry' does not have an 'a' in his first name, so he is excluded (or filtered) from the results

Collection we display on the WPF form.

If we now look back at the code we can see when the LINQ query is defined, and when it is 'actioned' causing the

query to be executed (or run). Below is a reminder of this code :-

================================================================================

Look above at line 225, and see that the variable 'someStudents' is being used on the RHS of the assignment.

This is what causes the LINQ query to be run.

Now let us change / amend this code slightly, to get a different result - check the following details :-

=============================================================================================================

Although we have defined the LINQ query before we add a fifth student to the full list, when we assign the variable

'someStudent' to the DataContext property we see a fuller list display than previously.

So that makes good sense with the idea that the query is only run (executed) when the Collection data variable

is used (required) on line 230.

Lets now change this experiment to another similar one, to prove something else interesting.

If we convert the query results into a 'List' object just after the query definition code, we see this below :-

=============================================================================================================

First of all, above we see we have gone back to only three students displayed in the DataGrid control.

This is because line 225 forced the query to run and then the results copied over into a List object.

On line 232 we are binding the new Students list, so we are not forcing a second query execution.

Moving on a step or two ..............

See the following newly amended experimental code, and try to work out what is going on ;-0)

==============================================================================================================

The extra 'DataGrid' control is pink and on the RHS.

The simplest way to analyse what is going on is to find the lines in which 'someStudents' appears on the RHS of

the ' := ' assignment.

Lines 225 and 232 are the only two where this occurs. So the query is evaluated twice.

So we can clearly see that 'myStudents' evaluated on line 225 is 'fixed' or 'frozen', in that a second

evaluation of the query leaves it's value unaffected, and is shown in the LHS data grid with three data lines.

We need to remember this as similar lines to that of 225 can be used by use regularly, when we don't wish

the query evaluation to take place a further time after the first evaluation.

It is common to wish to limit query evaluations, particularly when using SQL and Entity code to solve problems

in our business solutions.

===================================================================================

Something we need to know, the query results collection does not implement 'IList'.

This means we can't apply and use indexing syntax using square braces, ' [ n ] '.

If we wish our Collection to do so we then have to use the 'ToList()' extension method.

[ .. is this an extension method ? .. ]

Study the following image to see that this is correct :-

==============================================================================================================================================

PHH - 10th. December 2017.


Navigation: LINQ - processing Collections >

Return to chapter overview

'Chaining' queries - making life easier

PHH - 13th. December 2017.


'Method Syntax'

Whether we like it or not, there are times that we MUST use 'method syntax' - checkout below :-

====================================================================================

The term 'Method Syntax' is actually 'Extension Method Syntax'.

Extension methods can be chained together, one after another. The data output from one becoming the

data input of another (the next one).

CARE ... Some (one or two) methods do not provide data output !

Also, when we use Extension Methods we do need to use 'Lambda Expressions'. This frightens some guys,

it used to me ;-0)

Now I realise that I need to become reasonably proficient in their use and general creation or writing.

The better our Lambda skills the better our LINQ queries, and that is not a bad aim for us as coders.

Here is a bit of Lambda 'stuff' from the introduction in the previous subsection :-

==============================================================================================

Unfortunately this is written in C# syntax so I will go and generate some X# for us to see.

If we find some suitable working examples then we can rewrite the above Method Syntax ourselves

but in X# syntax. Lets see what we can manage ..............

Check this out in detail :-

And we need details from the following too :-

================================================================================================================================================================

Here we go with our code and syntax conversion :-

[ .. the following is live working X# code .. ]

================================================================================================================

The above code first creates a Customer typed List for use within the LINQ query.

Note the LAMBDA syntax in lines 147, 148, and 149. Note particularly the extra '{ }' around the Lambda characters.

I think these '{}' are for instantiating the Code Block that X# uses.

The 'CLASS' and the more '{ }' is to create an 'Anonymous Type' for the element made up of the three string items.

The Customer class required for the testing purposes is shown below :-

=========================================================================================================

Okay then guys, we are on our way to being able to write correct and working Lambda 'Extension Method' syntax in

our beloved X# code.

Lets move on a few more steps ..............

PHH - 30th. November 2017.


Understanding the Lambda 'variables' used

Okay then folks, if we are to use 'Method Syntax' in our LINQ queries then we must be able to fully

understand Lambda Expressions - both their syntax and the 'variables' that are defined, named, and used.

When I use the term 'variables' for Lambda expressions I mean the following - see below to get us going

with the discussion :-

[ .. 'input parameters' are the official name of what I call 'variable names' .. ]

=================================================================================================

Okay then, lets look at and study, line 181.

In the 'where' extension method we see two input parameters, these have been named by me as 's' and 'index'.

Although we could have named these anything unique, it seems a sensible idea to give them appropriate names.

By appropriate I mean a name related to what they are, or what the do.

So, for my code above, 's' stands for string, and 'index' for the index value in the List of strings.

It would appear that when we specify an input parameter (or two), the first one must represent the element in

the Collection.

Line 182 is easy as it only has one 'variable' and so it must be the string element.

One thing we need to checkout is which Collections will not allow us to use 'index'.

Do all Collections allow us an input parameter representing the index value.

[ .. remember .NET is zero based for collection indexes .. ]

We need to try ArrayList, Stack, Queue, ObservableCollection, Dictionary, but we do already know that both

List and Array support the indexing syntax.

Let's try out some code with an ObservableCollection<T> - study the following carefully :-

==================================================================================================================

So "YES" we see that we can use index values and the indexing syntax.

Also notice above that we tried out the zero base behaviour as well.

We could have equally found out this information by studying the eNotes from Microsoft.

All class types in the namespace of 'System.Collections.Generic' support, or adhere to, the 'IList' interface.

Here is part of the eNotes entry for this namespace :-

======================================================================================================================

Okay then guys, from the above experimentation and eNotes research it would appear that all generic Collection

types have this 'IList' interface and so must be able to have an index related input parameter in any Lambda expressions

we care to create / design and use.

To find out more about Collections and how they operate etc., checkout the section in these eNotes on 'Collections'.

The first three sub-sections deal with general information on Collections which we all need to know.

PHH - 11th. December 2017.


All of the possible Extension Methods

Google 'System.Linq' namespace - a good place to start :-

https://msdn.microsoft.com/en-us/library/system.linq.enumerable_methods(v=vs.110).aspx

==============================================

================================================

If we now use the intellisense facility in Visual Studio we get these available methods plus quite a

few more as well.

'Add' for instance, seen below, is not included in the 'Enumerable Methods' - these start at 'Aggregate'.

Lets see which EMs we can spot in the intellisense drop lists :-

===============================================================================

Notice that above the tooltip does not include 'IEnumerable' and yet it does below :-

================================================================================================================================================

Also notice that this differential information is included in the little icons at the left of the text.

Compare the two icons in the following image :-

==============================================

===============================================================================

The little black downwards arrow is associated with the EMs we are discussing - see below for more :-

==========================================================================================================================================

And here are a few more - some very useful methods indeed :-

====================================================================================================================================================================================================

And here are a few more - some very useful methods indeed :-

===========================================================================================================================================================

There are so many useful EMs, the list goes on and on ............

=====================================================================

Good hunting, and do make plenty of time to carry out coding experiments, so you

can get a real good feel for what needs to be done to get the most from EMs.

Checkout my eNotes in the 'LINQ to Objects' section of Appendix 'X'. There are loads of

good examples to be seen.

https://msdn.microsoft.com/en-us/library/bb351562(v=vs.110).aspx

============================================================================================================

Lets move on ..............

PHH - 30th. November & 15th. December 2017.


A useful example

Remember that in X# we need to code the C# of :-

" c => ........ "

as

" { |c| ......... } "

and that includes the extra curly brace for those guys who like C# syntax ;-0))

============================================================================================================

============================================================================================================================================================

====================================================================================================================================================================

=====================================================================================================================================

PHH - 15th. December 2017.


Distinct() - use with business class objects ...

'Extension Method' is written below as 'EM' or just EM :-

One of the very useful, but simple, extension methods is the 'Distinct()' 'EM'.

It basically allows us to return data sets with no duplicates - everything appears only once

in the returned Collection.

Having said that, most book and web examples tend to show it being used in very simple, or

straightforward ways, with simple data types. For example finding a list of names which are strings.

Most of us need to be able to use the 'Distinct' EM with more complex data types, that of our own

business classes.

Here is an example of mine which has just come from some work I am doing with Report Printing.

The business class is the reasonably simple 'Person' class, with these public properties :-

=============================================================================

This is how we wish to use a Collection of such class objects - check line 121 in particular :-

===============================================================================================================================

So basically we are after single individuals who reside in any of the UK countries.

Lets see some good results before we look at the code required to allow this to happen.

Check out these nice screen results :-

===================================================================================================================================

Yes, well we certainly don't seem to have any duplicates.

Even Alwyn is not in the list more than once ;-0)

What is our problem or issue when we need to use 'Distinct' with complex class objects.

Its is quite simple really - when is one object 'equal' to another object ?

Or 'what does equal mean' for a Person object in our example here ?

The answer is also quite simple, its what we make it - we have to decide.

To make a start we could say that two data entries are the same if their First and Last

names are the same - equal that is.

So using some Microsoft web sample code to copy and adapt, here is what we need :-

[ .. note line 213 has the interface which is being implemented .. ]

=================================================================================================================

Both these methods need to exist and be filled out with meaningful code lines.

I just copied and translated the C# code to get it to work for me :-

======================================================================================================

If we worry about more than one person having the same names - like "James Smith", then we can add a

third property value, say the DoB (date of birth) property.

Here are the extra lines added at 240 and 241 :-

======================================================================================================

The results here for my example are no different from previously - BUT - the code does work as shown

AND this also suggest that we should make a test by adding another Person to the sample data set.

Lets try this - we will add James Smith twice, with different dates of birth :-

==========================================================================================================================

Okay then guys, we see the new data shown above.

Well, its a good job we did a proper test, as I had wrongly read / understood the code line 243 - sorry !

The caret / up arrow means 'Xor' apparently, and so I got only one James Smith in the list.

Here is some MS info. :-

=======================================================================================================

I had mistakenly taken ^ to be a mathematical / numerical operator as it sometimes is / represents.

Therefore I though the result was a mathematical combination of all three hash values - nor one or the other etc.

So lets see the right results as far as I require then in my example - check these out :-

At the very bottom we have both our James now ;-0)

So our new code test for equality seems to be working, and using all three of the hash values.

Here is the new code which works :-

=================================================================================================

So now we can also test out the 'just UK guys' button, as that too uses the 'Distinct()' EM :-

Once again we see we get both the James Smiths but who are different people, with different dates of birth.

Lets move on .................

PHH - 23rd. March 2018.


Navigation: LINQ - processing Collections >

Return to chapter overview

Extension Methods

PHH - 1st December 2017.


From the appendix 'X' in ClickStartLINQ

https://msdn.microsoft.com/en-GB/library/bb383977.aspx

=====================================================================================================================================

https://msdn.microsoft.com/en-us/library/bb397947.aspx

=============================================================================================================

===========================================================================================================

Okay then folks, lets use some XSharp syntax to make a working sample of both the 'Query' and 'Method' syntax

shown in the MSDN eNotes on this topic - the provided sample from MS is just below the text snippets seen above.

Check out lines 332 through 336 with the content of line 347 :-

============================================================================================================================

Yes, the console output shows that each of the two syntaxes does the same job for us.

We can of course structure the Method syntax in a similar way to the structure I have used for the 'Query syntax. It

does make it easier to read, particularly when we have a number of 'chained' methods.

How do you like this code structure ? :-

==================================================================================

Yes, I like it AND it still gives the correct answers ;-0)

While we are at it ! :

Hey guys, lets do something else while we have a collection of integers.

Lets take the original object, the array of integers, and simply apply an extension method to find ourselves

a few interesting values.

Check this out :-

===================================================================================================

==========================================================================================

PHH - 15th. December 2017.


Sum() - twenty overloads, List<T> !

Line 327 in the example shown below uses one of the twenty available overloads for Sum() that

are listed in the Extension Methods of the class List<T>.

Checkout all these details :-

============================================================================================================

PHH - 1st December 2017.


The 'Clauses' available

let

where

select

orderby

from

groupby

PHH - 30th. November 2017.


Using the 'Let' clause

From postings in 'Pearls' in the X# Forum :

Here is a nice X# example of the 'let' clause in a query syntax line.

The let gives us a way to specify a variable in out query code line, and then re-use it. I have also used a static

method or two in this code - we may need to be careful in using Methods in query LINQ code for L2S and L2E

as the code needs to be able to be expressed in T-SQL script, so we can't just use any old .NET function/method.

More fun in the morning, first here are the 'let' images :-

====================================================================================================

=================================================================================================================

PHH - 30th. November 2017.


A good working example

As you may imagine, it is not always that easy to get a good working example to illustrate,

or demonstrate, a topic being discussed.

It is therefore with pleasure that I include the following sample / example of the 'Let' clause

making something work for us - that would otherwise be much more awkward or difficult.

I suppose the alternative (which I started with) is more of the messy and less readable side, as

opposed to difficult.

In the following image check out lines 47 through 50 to see two 'Let' clauses in action :-

===============================================================================================================================================================

These two clauses allow us while in the data query loop, find the end of the Folder string in a pathname,

and then chop off the LHS of the full path to leave the filename. Very handy to have this in one iteration.

PHH - 15th. December 2017.


Compiled query expressions - 'how to'

===================================================================================================================

PHH - 1st December 2017.


'Anonymous Types' - use of, how and why

There is already quite a body of useful information under the LINQ sub-folder of the 'Collections'

main folder - above it, in the contents LHS pain.

Here, in this sub-section, we will try and include extra information and further working samples.

General and Personal :

I first came across 'Anonymous Types' when I needed to use LINQ querying for my data solutions

using 'Entity Framework 6'.

Basically, we can't use 'EF6' without using LINQ and 'ATs'.

At the time Vulcan.NET did not have the functionality built into its compiler, and so I went to C# for

my code projects.

Now that we have X# built on the 'Open Source' Microsoft 'Roslyn' compiler this shortcoming has been

put right.

With Roslyn underpinning the X# compiler (as it were) we can now do anything in X# that we can do in C#,

on the .NET front that is.

So on a personal 'note' I only use Anonymous Types because I now use LINQ heavily (throughout?) in

my X# coding when dealing with data and Collections and even Arrays too.

In a modern .NET world we MUST use Collections, and so we definitely ought to use LINQ, and if we

use LINQ well we will need to call upon 'ATs'.

To be honest guys, its no good at all you running away from these things - just roll up your sleeves and

learn all that you need to know by reading and following my eNotes.

Oh! and this means we need to use both 'Query Syntax' and 'Method Syntax' and this means being able

to read and write Lambda Expressions.

If I can do these things, then you can also. It is all quite sensible and NOT rocket science ;-0)

============================================================================

The 'WHY' of things :

Okay then guys, lets say my explanation above is the 'WHY' - read it carefully and it basically says that

we have no choice - we need to use Anonymous Types with our LINQ, unless we make things difficult for ourselves.

Oh! and it is the fact that others will use 'ATs' in sample code found for C#.NET while doing searches and browsing.

We need to be able to read 'AT' code as well as write it.

" 'WHY' ? - because we have no choice ! "

The 'HOW' of things :

Basically the 'HOW' is the code syntax in X# to create an instance of a new Type (class), without a name and

also a class definition, and use the instance in the code following the instantiation.

We also need to know how to pass this AT around in our code, for example, as a return from a Method.

Lets now do a simple test to code a simple 'property' structure of a new 'AT' and supply a couple of data values

at the same time. Checkout the details shown below :-

=======================================================================================

In the above X# code we have an instance called 'oTestAT' with two properties 'Name' and 'Value'.

We have supplied the values of the properties at the time we defined the new Type / Class.

So then folks, what 'Type' does the system think we have ? What Type is 'oTestAT' ?

Lets take a look - see below for details :-

=============================================================================================

Wow!! - so the System has provided a suitable name for the AT, it needs to be unique and unlikely to

clash with what we as users may use ourselves in our Business logic.

I think the class type is the character string before the first (open) square bracket / brace.

A more usual way, or place in code, to create and use an 'AT' :

Quite often while using LINQ queries (*of any description*) we need to make an AT 'on the fly' as we collect

together various properties from a LINQ statement, or query.

Here is a real example - check lines 95 and 96 :-

=================================================================================================================

Reference :-

=========================================

* any description means - L2S, L2O, L2E .................

** L2S is LINQ to SQL,

** L2O is LINQ to Objects,

** L2E is LINQ to Entities.

=========================================

In the image above, it is the second half of line 96 which is the code for the AT creation.

' class {s,i } ' causes an AT to be made with one property from 's' and another from 'i'.

Since we have not specifically provided a name for the two properties then the system attempts

to name them itself - with a VERY uninspired algorithm.

But since we are probably best advised to always name our own when we remember ;-0) - we can

come up with more interesting names.

Here is a working example of what I am suggesting - try to spot the property naming lines yourself :-

===============================================================================

It is obvious when we print the Collection items that we have had better success than the naming

algorithm would have done.

Oh! and sometimes the naming process MUST have an input from us the developer / coders.

In the above example, only the first two of the total seven properties can be auto named - the

rest need us to supply a name.

Do realise however, that we can virtually name each property anything we choose, as long as it

is not the same as another property on the AT class, and that we obey the naming restrictions

of .NET coding.

What does an 'AT' creation look like in C# ? :

Well guys, I did promise that we would see little (if any) C# code and syntax in these eNotes on

X# - BUT - there are times when we need to know how stuff looks in C# so that we can take

sample code (and example projects) and make them work for us in X#.

So with that in mind lets look at the C# equivalent for making ATs.

============================================================================================================================

So if we now look out for this above syntax we may see code like this :-

var expr =

from c in customers

where c.Country == Countries.Italy

select new { c.Name, c.City };

It is the last line which is making an AT with two properties.

Because we are using a complex type already, then the property names 'Name' and 'City' will be

picked up (found) by the system naming algorithm.

So the new At will have two string properties called 'Name' and 'City'.

Enough of C# code and syntax ;-0)

Lets move on ..............

See you in the next sub-section ..............

PHH - 8th. December 2017.


"Passing" 'AT's - between members and more ...........

Okay guys, so what's the problem ?

Well, its one thing writing code to create an AT, but its another to be able to pass that instance

of the AT from inside one Method, back out to the calling code.

Or for that matter, to input an AT into a Method or Constructor, as a parameter.

Why ? - well simply because we can't know what the 'Type' is when we write the code.

Strongly typing our code (as in .NET) means we do need to know the Types of our data parameters.

So we need to look in detail at this issue, and if nothing else find a smart way or more to deal

with the problem in a way that does not compromise our code design.

Let's find an example to show what I mean .............

Starting with the easiest way - most likely to succeed.

Return a List<T> where 'T' is a defined Type :

We need to realise that if as a result of our LINQ query we can end up with a results Collection which

is a List, and the type of the List elements are instances of a known (and defined) type, then this will

work everywhere we wish to pass it.

So if your LINQ query result is going to be used in many places it is probably best to make it List<T>

where 'T' is NOT an anonymous Type.

However, don't lose the benefits of using ATs for simpler situation, we just need to know what can be

done, and how to do it.

Let us study a simple example of the above approach :-

=============================================================================================

In the above code we define a method which runs a LINQ query and returns a collection of objects.

The Collection is a generic List of 'NameIdenity' objects. So our defined Type is 'NamesIdentity'.

This helps explain what is returned :-

=======================================================================

And now we need to see the supporting class definition - see below :-

============================================================================

And also we need to see the code which causes the LINQ query to be executed.

Check this out :-

======================================================================================================

Oh! - nearly forgot, we need to see the 'StudentClassSet' static method which supplies the manually

typed data we will eventually see in a 'DataGrid' control- check this out :-

==================================================================================================================

The above code is quite straight forward, but you may like to remind yourself how the syntax looks

for creating an instance and providing property data.

.... Student{ }{ .... }

Yes, we have two pairs of curly braces, the second takes the data for the properties, the first just means create

an instance of.

Also spot (notice) the List<int>{ } {... } on lines 244, 248, 252, and 256.

Lets move on a pace or two ...............

We won't see all of these four Student data records on the WPF form, as we are applying a filter.

Now finally, we need to see the displayed data which has been returned :-

================================================================================

Notice each record has a First (name) containing an 'a'.

Well, that seemed simple enough - and it works.

Lets move on to something a bit more complex ..............

Return a List<T> where 'T' is an anonymous type (AT) :

From a method which defines and executes a LINQ query, it is always a pretty good idea to

return a generic List object whenever possible.

Access to elements within a List is easy and flexible, and remember we can even use index

syntax of the double square braces - ' [ n ] '.

Return a Query Collection of a defined Type :

Return a Query Collection of an anonymous type (AT) :

Using the 'DYNAMIC' type to help us over our problem :

*** CARE *** you are likely to need a reference adding to your references section - see below :-

================================================

When you do not include this reference you are likely to see this sort of thing :-

=======================================================================================================================

And the above compilation error is simply due to me trying to do a 'ToString()' on the Dynamic object itself - without

the 'Microsoft.CSharp' entry in the References section.

So be warned!

=========================================================================================

PHH - 9th. November 2017.


Lambda Expressions - the whole story!?

==========================================================================================================================

https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/statements-expressions-operators/lambda-expressions

Okay then guys, after a bit of a struggle and a fiddle around I have the basic X# code syntax to make and

use a Lambda Expression, I think ;-0)

Study the following :-

========================================================================================

Well, we at least know that 13 squared is 169, but then we knew that before we wrote any code.

I will need to think about what I have achieved, so we can get the most out of this little piece of success.

Okay then, I just had a thought, lets try just changing a name or two - check this out :-

=====================================================================================

We have basically written a 'function', or what we used to call functions in years gone by.

Replacing the name 'myDelegate' with 'FindSquareOf' makes a lot more sense of the code.

It then also suggests to me that we can write something more useful like this :-

=================================================================================================================

In the above code lines 180 through 182 we find the array elements which are exactly divisible by 3 and square these.

At the moment (currently above) we just output the number in the result list.

Lets now use some method syntax and find the sum and the maximum value as well - check below :-

=================================================================================================================================

And the third and final output - note that these three methods take no input parameters - now for 'Max :-

====================================================================================================================

Lets move on ...............

Okay then folks, lets be honest, we could easily have coded the above solution without need to reach for

a delegate and a Lambda expression - BUT - what about when the numerical calculation is a more complex

formula - AND - we wish to pass the delegate into other methods that we write !?

Lets first try a complex formula - this is for a third degree polynomial.

==============================================================================================================================================

Here is the changed declaration of the the 'delegate' we are using with now five INT type input parameters.

========================================================================================================

Probably a better name for me to use would be 'del5ints', however there are much better ways to define and declare

our Delegates, as we can use strong typing too I believe.

But, one step at a time, so lets just enjoy some (more) success, shown above, with our complex polynomial equation.

Moving on a step or two ..........

Let us define a small new Class, of type 'PolyForm', this will hold all value for the evaluation of the equation we defined

above in the lambda expression of the delegate - check these details out :-

=========================================================================================================

The values a,b,c, ad d are called coefficients of the polynomial equation.

We can now create an array of 'PolyForm' objects before we process the data and evaluate the equation.

Check out the new details in the following working images :-

=============================

So in line 254 shown below we actually do quite a bit of work ! :-

===================================================================================================================================================

This looks VERY promising !

Time for a break, and to also move on to sub-sections to see what else may be done with this

rather interesting Lambda related technology.

See you soon ........

PHH - 1st December 2017.


Current (new) syntax from X# Team

During the recent time I have been researching material on Lambda for the X# syntax eNote section,

Robert and his Team have made some useful changes. Changes that I feel help me in my LINQ and

Collection coding.

Here is what Robert posted recently :-

====================================================================

So this means we can now use syntax as shown below from a recent working sample of mine :-

===========================================================================================================================

Lines 73 through 75 replace the older style code syntax shown below :-

[ .. note the old 'stuff' still works .. ]

===========================================================================================================================

If you want my advice, I would suggest you take one syntax style and stick with it. Then there

is far less chance of making mistakes and errors - and preferably the new style, similar to that of C#.

Since it is the new style we will see in samples on the internet, then it seems like a smart thing to do.

Note that we can use any 'string' name for the Lambda variables, BUT, we must keep to an order. I mean

that we seem to have to define the element of the Collection first, and must include it even though we not

refer to it later in the expression.

See below for an example of some of what I mean :-

======================================================================================================================================

Let it be quite clear, I am no expert on the deeper issues of Lambda Expressions or LINQ either.

I simply deal with things at a code and syntax level, and make plenty of extensive experiments to see

what works, and what makes sense to a programmer / developer.

However, one can get quite knowledgeable by approaching things this way - knowledgeable in terms of how to get

'stuff' and 'things' to work.

Below we see that if we fail to include a term for the Collection 'element', then we get an error :-

=======================================================================================================================

In the above code I missed out on line 115 the code ... 'a as string'.

Now lets try line 116 on which we try to reverse the order of the 'element' and 'index' :-

================================================================================================================================

Yep! - still an error.

So lets keep to that which we know works, and NOT to try and be too "clever" in any way.

PHH - 8th. December 2017.


Using with LINQ

Using 'Lambda Expressions' with LINQ queries :

Let us revisit the working example of our English names - using a 'String' list.

The image of code shown below is that of a 'Method Syntax' solution - check lines 114 through 116 :-

======================================================================================================================================

According to Paolo Pialorsi and Marco Russo, ( 'Introducing Microsoft LINQ' ), we can't do the same thing

with a 'where' clause in LINQ query syntax.

It seems obvious that they mean that we can't code a 'query syntax' to access the 'Index' from the 'where' clause.

Remember, we can do 100% of what is possible with 'method syntax', but less than that using 'query syntax'.

But 'query syntax' is more readable, and hence quickly understandable.

This simply means that we need to know how to read and write both forms of the syntax for LINQ.

This also means that we can then understand any useful code samples and snippets that we find on the 'net'.

PHH - 1st December 2017.


Using outside of LINQ

Using 'Lambda Expressions' outside of LINQ queries :

If we are not using Lambda Expressions in our LINQ querying, then what else may we use it for ?

What else will it do for the standard coder guy ?

PHH - 1st December 2017.


Navigation: »No topics above this level«

Return to chapter overview

Events and Delegates

PHH - April 11th. 2017


Why Events, and where did they originate ?

PHH - April 11th. 2017


OOP - and three pillars ...

Here is what I am using currently :-

===========================================================

The start of the search I made :-

======================================================================================================================

Yes, we need to take on board Encapsulation, Inheritance and Polymorphism.

*** CARE *** Stuff on the web on Three Pillars is usually one guys interpretation !

===============================================================================================================================

Wikipedia says this :-

Encapsulation :

Encapsulation is an OOP concept.

It describes when a class holds together the data and functions that manipulate the data, keeping both safe from outside

interference and misuse.

Data encapsulation led to the important OOP concept called 'data hiding'.

If a class does not allow calling code to access internal object data but permits access through methods only, then this is a strong

form of abstraction or information hiding known as encapsulation.

PHH - April 11th. 2017


Navigation: OOP - and three pillars ... >

Return to chapter overview

What the standard X# guy needs to know

PHH - April 11th. 2017


Navigation: OOP - and three pillars ... > What the standard X# guy needs to know >

Return to chapter overview

Inheritance

PHH - 27th. October 2017


Navigation: OOP - and three pillars ... > What the standard X# guy needs to know >

Return to chapter overview

Polymorphism

PHH - 27th. October 2017


Navigation: OOP - and three pillars ... > What the standard X# guy needs to know >

Return to chapter overview

Encapsulation

PHH - 27th. October 2017


Navigation: OOP - and three pillars ... >

Return to chapter overview

Don't make this too complicated

PHH - April 11th. 2017


Navigation: OOP - and three pillars ... >

Return to chapter overview

What is multiple inheritance ?

PHH - April 11th. 2017


Navigation: OOP - and three pillars ... >

Return to chapter overview

Why do we have Interfaces in .NET

PHH - 27th. October 2017


Data

Lets face facts, not many applications these days run without data of some sort or another.

For us X# Forum guys we probably handle more data that less, and most of our data is of the

general type called 'business data'.

Also, most of our business data is probably very suited to a relational database back-end like

Microsoft's SQL Server, or similar. There are a few corporate SQL alternatives out there.

The pattern of development Microsoft has built up and recommends to us is called the 'Disconnected

Model'.

This translates simply into the fact that we connect to a SQL database and query it to receive back

some data and then disconnect once more. Data is processed in memory while the connection to

the server is 'down'.

When we have data in memory most of us have Collections of business types of objects, like Customer,

and Order, and Product.

If we can handle lists of business objects in 'generic' Collection in a smart way then we are half way

to writing good applications in today's .NET world.

Finally, our business class objects are complex data structures and as such are often built up with

Properties that are simpler (basic?) data types, or indeed further business class objects.

When it comes down it it eventually everything, at the end of the tree, are simple data types.

We will see many useful examples which demonstrates the above general data structure.

AND - like it or lump (hate?) it, we all need to use LINQ to handle and process our data !!!

AND further - if we use LINQ then we MUST understand Lambda Expressions, Extension Methods,

Anonymous Types, and even Entity Framework 6 and beyond (when it arrives).

All of the above technologies seem to fit together and Microsoft have done a good job in their development.

So let us roll up our sleeves and start learning ;-0)

We shall start in the next sub-section with simpler data types.

See you there ...................

PHH - April 11th. & 13th. December 2017


Simple types of .... includes Value types

Many of the simple data types are termed 'Value Types'.

Some commonly used simple data types include the following :-

Øint

Ødouble

Ødecimal

Ølogic / Boolean

ØDateTime

Øenum

Østring

Here is a table of the .NET Value Types :-

====================================================================

Notice that 'String' is not included, so its not a Value type.

There is plenty of information on these across the net and in Microsoft eNotes generally.

Go check this out if you need more information.

What we should do now is write and test some X# code.

Here is an easy start to things :-

=======================================================================================================

And here is the working result for the String class test :-

========================

And now the result of the DateTime 'structure' test :-

=================================

Note in the above code image that 'int', 'Double', and 'Decimal' do not really need more explanation,

or even print out - their syntax simply compiles as seen.

Now then, lets see what Microsoft says about 'int' or 'Int32' - see below :-

============================================================================================

And now what does Microsoft say in "Help Viewer 2.2" about the 'DateTime' structure ?

Study this :-

=================================================================================

Note below that MS says that the Double value type is a structure :-

=============================================================

BUT - guys, the String type is a Class - check this out :-

============================================================================================

And being a Class 'String' has some Constructor overloads - study these details :-

======================================================================================================================

Okay then guys, enough with strings as they are commonly used and we tend to know more about them.

Lets now look at a more interesting Type which is less commonly used but sometimes quite vital to our code.

We shall now explore the 'TimeSpan' structure - checkout below :-

====================================================

And more information from MS - on Constructors - is shown below :-

====================================================================================================================

Read the following remarks before we write and test some exploratory code :-

===========================================================================================================================

Okay then folks - lets try some very simple code in X# syntax :-

===============================================================================================================

Out of interest to you I have just run the above code on a newly installed version of the X# compiler - version 1.1.1.0

I am using Visual Studio 2015 Professional with this new version - date is 13th. December 2017.

We will be coding in X# a sample form the MS Help Viewer 2.2 eNotes - these are the C# results :-

==============================================================

Now let us see what we can do in X# code - follow all the details below - CAREFULLY :-

=================================================================================================================================================

Yes, it looks as though we have replicated the test code and produced the same results.

Time to move on ................

See you in the next sub-section.

PHH - 12th. & 13th. December 2017.


String Class - in more detail

PHH - 13th. December 2017.


enums - Enumeration Types

*** NOTE *** There is a full section in the contents on 'Enum's !

Currently this section is next, after that on 'Data'.

======================================================================================

.NET is full of 'enums' - seen when we search the Framework namespaces.

Thousands of them, so it seems a good idea to define and use a few in our own business code.

I would hazard a guess that most of us don't use them enough, simply because we haven't got into the

swing of things 'enum'.

Try to use them more, but only where appropriate. Checkout below what Microsoft has to say :-

===================================================================================================================

====================================================================================

Lets now look at the MS example, but using X# code syntax instead of C# :-

In this short section on 'Enums' we will concentrate on their use as data within our own apps.

There are many instances where we need data values - BUT - limited within a small number of

fixed possible values. We are going to look at one such sample from a working app I developed for

the German IT conference in Cologne in 2015 and 2016.

First samples from a working application :

The sample we will study is a large app on data binding, WPF, and also Entity Framework.

However, we are just going to focus on a couple of straight forward 'enum's.

First lets see an image of the SE (solution explorer) pane, I like to keep all my

'enum's in one place - check below :-

==================================================

So here is the .PRG file open with all the details showing :-

================================================================

As you can see there are only two, one called 'ExperienceLevel' and another called 'KnowledgeLevel'.

Both are integer types - with integer values.

Lets now see how they are used to define the business class called 'Presentation'.

The 'Presentation' class is related to the sessions to be delivered.

===========================================================================================================

In the above code study lines 749/750 as well as 765/766 which cover the declarations and

the assignments.

Now then folks, by using enums, as well as restricting the possible values to correct and acceptable ones, we

also get intellisense which helps us while coding.

Intellisense is great for both user 'enum's and .NET Framework predefined 'enum's.

Here is intellisense working for my 'KnowledgeLevel' enum :-

=================================================================================

Okay then, it should be obvious from the above details and images how to define and use enmus.

For full details on other numeric values and strings see the fuller section later in these eNotes.

Make sure you checkout the use of Reflection to find Framework enums in particular - quite interesting.

Lets move on to another sub-section ...............

PHH - 13th. & 14th. December 2017.


Files / Folders / Discs

My personal feeling is that these are a necessary (old fashioned?) evil in today's modern IT

and coding world.

About as modern as the old 'Goto' statement ;-0((

However, we can't get along without them - SO - we may as well bite the bullet and learn how

to use then in code correctly.

Yes, I said 'correctly', as I do not sign-up the the idea (or comment) that there is no right or wrong

way to code apps.

Personally, I feel there are loads of wrong ways, and that Microsoft has a good idea of what their

experts will consider the right ways - or al least better ways.

So there is a long path to coding 'enlightenment' which is similar to a religious quest, or pilgrimage.

*** REMEMBER ***

When dealing with Files/Folders or Files/Directories, we are dealing with the 'outside' world.

We are dealing with hardware and stuff that may not be there when the code is run a subsequent

time. This is always more difficult for the developer, and CARE must be taken to do as many checks

as possible within the code. We can't assume a disc exists because it was there previously.

PHH - 15th. December 2017.


File Class

============================================================

===========================================================================================================================

PHH - 15th. December 2017.


FileInfo Class

===========================================================================================================================

PHH - 15th. December 2017.


Directories - ('Folders')

The modern terminology of 'Folders' which are containers for Files has to be translated into the

older fashioned way of talking about 'Directories'.

So all the 'stuff' we wish to do for Folder we do for Directories.

That means we search in 'Help Viewer 2.2' on the search string 'Directory' - and this is what we find :-

==============================================================

And here is the basic Class information :-

===========================================================================================================================

And now, out of interest to a problem I have currently, here are a few STATIC methods to find files in a directory.

Study the following details before we look at some working code :-

=================================================================================================================

So it would appear that we can easily find '.DLL's this way in a specified folder / directory.

After quite a bit of development coding, to make my code solution modern and using much of the

new stuff in X#, here is a working sample for you to study - we will discuss it later on, when your studying

is completed.

===============================================================================================================================================================

Oops! - I have just noticed that lines 47 and 52 have a discrepancy with regard to the 'case', and yet I thought

my property settings were set so that Case Sensitivity is turned on - TRUE !?

I will have to talk with Chris and the Team - see below :-

=============================================================================================================

Answer from Chris and the Forum is that it is not as yet implemented in the current compiler.

It is a bit tricky, and it may take a while to get around to this as there are so many more pressing issues.

PHH - 15th. December 2017.


Navigation: Data > Files / Folders / Discs >

Return to chapter overview

Streaming

PHH - 15th. December 2017.


Navigation: Data > Files / Folders / Discs >

Return to chapter overview

Simple examples

PHH - 15th. December 2017.


Navigation: Data > Files / Folders / Discs > Simple examples >

Return to chapter overview

working code

PHH - 15th. December 2017.


Navigation: Data > Files / Folders / Discs >

Return to chapter overview

Further examples

PHH - 15th. December 2017.


Navigation: Data > Files / Folders / Discs > Further examples >

Return to chapter overview

working code

PHH - 15th. December 2017.


Navigation: Data >

Return to chapter overview

Collections

PHH - April 11th. 2017


Navigation: Data >

Return to chapter overview

LINQ

PHH - April 11th. 2017


Navigation: Data >

Return to chapter overview

Lambda Expressions

PHH - April 11th. 2017


Navigation: Data >

Return to chapter overview

Extension Methods

PHH - April 11th. 2017


Navigation: Data >

Return to chapter overview

Anonymous Types

PHH - April 11th. 2017


Data Binding

PHH - April 11th. & November 29th. 2017


Care with STRINGS !

You need to be fully aware of the contents of this sub-section, as it is very easy to be caught out, and

waste a lot of time and energy !

While even try some very simple STRING data binding we can get what looks like strange and/or incorrect

output in the WPF form.

=======================================================================================================================

===========================================================================================================

====================================

======================================================================================

===========================================================================================================

================================================================================================================

===========================================================

PHH - April 11th. 2017


Tracking property changes

PHH - April 11th. 2017


Entity Framework 6 and onwards

PHH - April 11th. 2017


Navigation: »No topics above this level«

Return to chapter overview

Enums - their common usage

PHH - April 11th. & December 14th. 2017


the .NET 'Enum' class

the .NET 'Enum' class

Lets look at what the Developer Network documentation has to say - we will pick out just the basic parts :-

==================================================================================================

And now lets see the STATIC methods which will provide us with most of our usable functionality :-

[ ... in our examples we will see most of these in action ... ]

========================================================================================================================================

Lets read on in the Microsoft eNotes :-

=====================================================================================================================

Obviously, we need to write some X# code and create some good examples, but easy to both read and understand.

Take this on board and REMEMBER it :-

=================================================================================================================================================

=============================================================================================

In order to get a 'feel' for the way we need to code 'Enums' in X#, lets take the simple (but commonly

used) example of the days of the week.

There are seven, we will never get more or less. Hopefully !

Here is one syntax to define a 'Days' enum :-

================================

And now another with a small change :-

=======================================

And another with even more changes :-

========================================

[ .. we do not need to use zero '0' .. ]

And we can even break away from convention and do this :-

========================================

Above I have given a value in-line with how I rate the day on a scale of 1 to 100.

Lets now move on a pace or two ...........

How do we retrieve enum values ? What values can they be ? Check below :-

==============================================================================================

But in fact we can get a string data return from the enum itself, rather than me hard coding it.

Study the following carefully :-

======================================================================================================

Now then folks, one of the BIG positives about 'Enums' is their use by intellisense.

Below is intellisense in action - we get the value options in alphabetical order it would seem.

Check this following image out thoroughly :-

===================================================================================================

Okay then, all good stuff !

We will see a lot more Enum use as we study other parts of the X# language and syntax.

Time to move on.

See you in the next sub-section.

PHH - December 14th. & 15th. 2017


System Enums - how to find by Reflection

Okay guys, lets have some fun with System defined Enums.

If we think we know little about these (and that is probably the case) then the more interesting

it is to see just what can be done in a short time and just a few lines of code!

Lets get to work - roll up your sleeves ...........

"System.IO" Namespace :

Okay folks, at the top of my PRG file for a test project I am developing I have the following :-

=====================================================================

Above we see on line 18 that 'System.IO' has been included as a Namespace to be accessed.

If we search the Microsoft eNotes information on this namespace we find the following details :-

====================================================================

Lets us choose 'FileOptions' from the list available to us in the 'System.IO' namespace.

We need to see if we can code a few lines of X# to be able to find and display the full details of

the 'FileOptions' enum.

Here below, we can see that one line of code (line 36) returns the seven ' 7 ' values :-

===============================================================================================================================================================

It would appear that we do not need to specify the namespace because it is available to the PRG in

which the code is being run / executed. We will explore more on this aspect later.

Now then, how do we get to see more details of the seven list items ? lets try a FOREACH loop.

Study these details carefully :-

=======================================================================================================================================================================

Okay then guys, looks good - BUT - is it correct ?

Lets see what the Help Viewer eNotes has to offer us :-

========================================================================================================================

Yep! - seems right to me ;-0)

Before we move on to see if we can find all the Classes and other stuff in a single namespace, lets see

if we can have success with a second 'Enum' we know of.

Lets look at some RegEx 'stuff'. First we need to include the namespace at the head of our PRG file :-

=================================================================================

Now lets copy and adapt the previous method to make a second one, to handle the RegEx options.

Study the following carefully and thoroughly :-

======================================================================================================================================================================

And now the listed results - see below :-

======================================

I will leave it up to you to find these options in the "Help Viewer 2.2" eNotes.

Moving on a step or two ...........

What we could do with is an app which finds and displays all the '.DLLs' in the Core folder, and

then when we select one in a ListView or DataGrid, the Enums are found and displayed in another

ListView control.

Finally, selecting one of the displayed Enums then finds and displays the enum options / values, in

yet another ListView control.

This way we can choose any DLL and be able to see enum options / values for any Enum in the assembly.

I need to go off and do some work - a little coding is good for the soul !

=============================================================================

PHH - April 11th. & December 14th. 2017


Our own coded Enums - making, and finding possible values

PHH - April 11th. & December 14th. 2017


When and where to use our own

PHH - April 11th. & December 14th. 2017


Assemblies and Namespaces : then Enums

Looking inside Assemblies :

Okay then folks, we have seen two Enums from the .NET Framework.

How can we find more for ourselves using X# code ?

First, let us look using File Explorer, at the folders on disc which contain the Assemblies for the Framework.

=============================================================================================

We are going to be generally most interested in the .NETCore folder, so lets see what it has to show us :-

========================================================================================

And then from the three options we will choose the first one, for v4.5 - lets study the following :-

=================================================================================================================

I have highlighted above the 'CSharp.dll' which I happen to have in use with my test project.

This project I mention above is one which finds the Enums in a DLL and the shows the options / values

for a selected Enum.

More of this in an earlier sub-section from here - two or three back.

=================================================

So if we change our method to use this alternative '.dll' rather than that for the Core, we get a new Method as seen below :-

==============================================================================================================================================================================

Running this adapted code we see the following :-

========================================================================================================================

Yes, content of rows/records would suggest that we are looking inside 'CSharp.dll'.

However, at the moment our code finds and display ALL of the Types it finds. We really just want to see

those which are Enums.

Lets see how we can filter the list we currently have.

=======================================================================================================================================================================

Well folks, the above code seems to work okay - only Enum types are displayed.

We can see the namespaces within the Assembly.

Lets try and count the total number - see the following :-

============================================================================================================

So we have 128 in the Core.dll, and now we need to count those in 'CSharp.dll'.

Here is the list data bound to the 'DataGrid' control :-

================================================================================================================================================

And this time the displayed count (seen below) is about half the total of the Core assembly.

========================

While we are at this WPF display task, and binding out results list to a DataGrid control. Lets see

what things look like when we get it wrong!

Below is NOT the way to do it ! - Remember, that data binding needs public Properties on the object

class being bound.

Surprisingly, the following two properties are the only ones for the String class :-

==========================================================================================================

So a simple list of Strings will only be able to display the Length property of the String class, like below :-

======================================================================================================================

So although we have a list of correct strings, correctly representing the names, we can't bind them

in this simple fashion.

Doing this simple way / approach with Integers (int32) is different as there is a suitable property.

The easy way around this is to use an Anonymous Type as seen way above on line 99. This as shown

gives us a public property and so we see the string data.

===============================================================================================================================

Lets move on ................

PHH - December 14th. & 15th. 2017


Navigation: Enums - their common usage > Assemblies and Namespaces : then Enums >

Return to chapter overview

Finding our own defined Enum details

PHH - 20th. December 2017.


from Appendix 'X' - of 'ClickStartLINQ' eNotes

I am transferring over to these new XSharp 'ClickStart' eNotes exactly what was originally included in

those I did before Cologne 2017 - which were added as "Appendix 'X' " to the 'ClickStartLINQ' notes.

This way we don't miss out on any useful and tested material.

Its too much of a task to integrate these original notes with the new ones I am now researching for the

full X# eVolume called "ClickStartXSharp".

There will be some duplication - but then, 'so what ! '.

==============================================================================

The .NET framework makes great use of Enums, and we will need to use these, whether we like it or not!

We have no choice but to use System enums - AND - if we are wise, we will make a few of our own as well.

Simply put, Enums make our programming and coding life easier. As well as making our code more readable.

===================================================================================================================================

====================================================================================

=======================================================================================================

===================================================================

========================================================================================================================

===================================================================================================================================================================

==============================

=================================================================================

==================================================================================================================================================================

Originally - PHH - 20th. February 2017.

PHH - 20th. December 2017.


the .NET 'Enum' class

From "Appendix 'X' - of ClickStartLINQ" :

Lets look at what the Developer Network documentation has to say - we will pick out just the basic parts :-

==================================================================================================

And now lets see the STATIC methods which will provide us with most of our usable functionality :-

[ ... in our examples we will see most of these in action ... ]

=========================================================================================================================================

=====================================================================================================================

Originally done - PHH - 20th. February 2017.

PHH - 20th. December 2017.


samples - a start

From "Appendix 'X' - of ClickStartLINQ" :

Lets make a start at discussing Enums and how I have recently been using them in my code.

I have been exporting older Vulcan code into X# ready for the Cologne 2017 conference.

Part of the Validation app from last year's conference (April 2016) uses a 'Mask' approach to an input TextBox

control on a WPF form.

Although I have not applied a 'Converter' to clean the value entered (but this is quite possible), the code I have

checks the data entered at the keyboard and informs the user if it is not compliant with the mask definition.

Lets us see a visual example - below is such a runtime situation :-

=================================================

In the XAML script shown below we see a wavy line - BUT - in fact it is not an error but is an issue caused by the current version

of the VS integration into VS 2017 RC. The script and code compiles and runs 100% a-okay!

==============================================================================================================================

So then folks, lets get to some Enum 'stuff'.

Here is an Enum of my own that I use with the Mask input code :-

[ .. note that some lines are collapsed, and they are similar to what is seen .. ]

===================================================

You may have noticed that the numeric part is actually one number representing two parts.

The first digit represents the number of alphabetic characters - shown as 'X's and the second,

or right hand digit represents the number of numeric digits.

So 22 would be represented as 'XXNN' and in the mask format this would be ' XX.NN ' which has

a dot (or point) between the characters and the digits.

Lets start to view and study some X# .NET code.

We will start coding examples by using one or two of the STATIC methods shown in the previous sub-section.

These static methods facilitate us when it comes to needs of manipulation of 'enum' members, their values, and

also their names.

Study the following image to see 'GetValues' in use as well as 'Cast()' and 'ToList()' extension methods :-

==============================================================================================================================================================

=========================================================================================================

PHH - 20th. February 2017.

PHH - 20th. December 2017.


samples - going further

From "Appendix 'X' - of ClickStartLINQ" :

=================================================================================

====================================================================================================================

==============================================================================

=====================================================================================================================

=====================================================================================================================

Originally done - 20th. February 2017.

PHH - 20th. December 2017.


samples - more advanced

From "Appendix 'X' - of ClickStartLINQ" :

=============================================================================================================

Originally done - 20th. February 2017.

PHH - 20th. December 2017.


samples - standard .NET enums

From "Appendix 'X' - of ClickStartLINQ" :

==================================================================================================================================================================

==========================================================================================================================================================================

======================================================================================================================================================================

=======================================================================================================================================================================

===============================================

====================================================================================================================================

Originally from - PHH - 20th. February 2017.

PHH - 20th. December 2017.


Assemblies and Namespaces

From "Appendix 'X' - of ClickStartLINQ" :

========================================================================================================================================================================

=============================================

================================================================================

======================================================

====================================================================================================================================================

Originally from :- PHH - 20th. February 2017.

PHH - 20th. December 2017.


Data Sources - 'Back-end'

Okay guys, lets be brave and face it SQL (structured query language) has just about won the day

for the present moment. No doubt this will change in time, but for now most business related applications

use one 'flavour' or another of SQL server.

As well as Microsoft's there are a few other heavy weights of about equal standing, as well as a few more

simple, or light weight versions which allow SQL statements to be used similar to the full blown installations.

"Oracle", "PostgreSQL" and "MSSQL Server" seem to come to mind straight away.

Here is a useful table from Wikipedia - although NOT a makers / suppliers list as such, it helps us move forward :-

======================================================================================================================

And the text (book) I have called "SQL in a Nutshell" lists these as the five common implementations of 'SQL2003'.

[ .. basically SQL2003 is a world wide specification .. ]

- IBM's DB2 version 8

- MySQL version 4

- Oracle Database 10g

- PostgreSQL version 7

- Microsoft's SQL Server 2000

In these eNotes for X# we will look in some detail at PostgreSQL and Microsoft SQL Server.

We will also look at a couple of light weight easy to install and easy to use versions of SQL.

These will be SQLight a .NET framework, as well as 'LocalDB' from Microsoft.

PHH - April 11th. 2017 & March 10th. 2018.


.NET access to data - ADO and Entity Framework ...

PHH - 10th. March 2018.


Navigation: Data Sources - 'Back-end' > .NET access to data - ADO and Entity Framework ... >

Return to chapter overview

modern access from .NET with LINQ and EF6 + EFC

PHH - 10th. March 2018.


MS SQL Server (MSSS) - install local and distant ...

This seems to be the worry of most developers out there.

They wish to be able to install a simple and light weight SQL facility on a distant machine.

Lets say one of their clients / customers at 'afar' - when they themselves are not there (on site) to do

an install of the SQL instance / facility.

PHH - 11th. March 2018.


Navigation: Data Sources - 'Back-end' >

Return to chapter overview

MS SQL Server - Full

PHH - April 11th. 2017


Navigation: Data Sources - 'Back-end' >

Return to chapter overview

MS SQL Server 'Express' ...

PHH - 10th. March 2018.


MS 'Express' - 'LocalDB' ...

Microsoft now provide a facility to allow a one connection SQL provision to be simply and easily

installed onto one PC / laptop.

This light weight SQL provision is called 'LocalDB' and is part of 'SQL Express', which is a free download.

The 'LocalDB' facility does not take up as many resources as the full 'SQL Express' itself.

And in turn the 'Express' version of SQL Server takes fewer resources than the Full 'MSSS' installation.

[ .. 'MSSS' is 'Microsoft SQL Server' .. ]

In many ways it is quite often 'handy' or useful, to have a lightweight SQL app running, without any change

in the X# code required.

Also, the datafile created, updated, used, by the 'Local' version is also exactly the same structure as the .MDF

files required for the full MSSS instance.

In this section we will look at some of these points, as we carry out an installation of 'LocalDB'.

At various points in the installation I will use MS (Management Studio) and VS (Visual Studio) to view and compare

the database and tables being created and edited / filled with sample data.

*** Remember *** this section is about the creation on a PC of a suitable and working 'LocalDB', and NOT the

detailed working of the SQL statements and the related X# code itself.

Lets look at the state of the Server shown below - before we do any installing. Check the list of attached databases :-

[ .. Stock is the last in the list and Adventure Works the first .. ]

==============================================================================

=================================================================================================================================

===========================================================================================================

======================================================================================

=======================================================================================================

========================================================================================================================================================================================================

======================================================================================================================================================

=========================================================================================================

===========================================================

====================================================================================================================

==============================================================================

============================================================================

======================================================================================

============================================================================================

===================================================================================================================

=======================================================================================

============================================================================

============================================

============================================================================================

=======================================================================================================================================================================

=========================================================================

==========================================================================

========================================================

=============================================================================================

=============================================================================

======================================================

============================================================================================================================

===========================================================

================================================================================

==================================================================================================

==============================================================================================

==============================================================================================

============================================================================================

==========================================================================

==================================================================

================================================================================

=============================================================================================================================

PHH - 10th. March 2018.


PostgreSQL

===================================================================================================================

===========================================================================================

=======================================================================================================================

============================================================

PHH - 10th. March 2018.


Navigation: Data Sources - 'Back-end' >

Return to chapter overview

Azure based data files ...

PHH - 10th. March 2018.


Navigation: Data Sources - 'Back-end' >

Return to chapter overview

SQLite - framework ...

PHH - 10th. March 2018.


Navigation: Data Sources - 'Back-end' >

Return to chapter overview

NuGet - how to for SQL 'stuff' ...

PHH - 10th. March 2018.


Navigation: Data Sources - 'Back-end' >

Return to chapter overview

DBFs - historic approach ...

PHH - 10th. March 2018.


Navigation: Data Sources - 'Back-end' >

Return to chapter overview

General data transfer with XML ...

PHH - 10th. March 2018.


Navigation: »No topics above this level«

Return to chapter overview

GUI forms - WPF

PHH - April 11th. 2017


Navigation: GUI forms - WPF >

Return to chapter overview

WPF

PHH - April 11th. 2017


Navigation: GUI forms - WPF > WPF >

Return to chapter overview

Value Converters

PHH - 29th. December 2017.


Navigation: GUI forms - WPF > WPF >

Return to chapter overview

Data Binding

PHH - 29th. December 2017.


Navigation: GUI forms - WPF >

Return to chapter overview

XAML

PHH - April 11th. 2017


Navigation: GUI forms - WPF > XAML >

Return to chapter overview

Data Binding

PHH - 29th. December 2017.


Navigation: GUI forms - WPF > XAML >

Return to chapter overview

Data Templates

PHH - 29th. December 2017.


Navigation: GUI forms - WPF > XAML >

Return to chapter overview

Strings and Escape Sequences

PHH - 29th. December 2017.


Navigation: GUI forms - WPF >

Return to chapter overview

Binding instead of Events

PHH - April 11th. 2017


Form Controls - some simple 'Binding'

PHH - 16th. December 2017.


Navigation: GUI forms - WPF > Form Controls - some simple 'Binding' >

Return to chapter overview

The 'ListBox' control and binding

PHH - 16th. December 2017.


Navigation: GUI forms - WPF > Form Controls - some simple 'Binding' >

Return to chapter overview

The 'DataGrid' control and binding

PHH - 16th. December 2017.


Navigation: GUI forms - WPF > Form Controls - some simple 'Binding' >

Return to chapter overview

Some basic experiments in code

PHH - 16th. December 2017.


A simple working example

=============================================================================================================================================================

===================================================================================================

PHH - 16th. December 2017.


Navigation: GUI forms - WPF >

Return to chapter overview

What does 'Data Binding' mean ?

PHH - April 11th. 2017


Navigation: GUI forms - WPF >

Return to chapter overview

'What and When' to Bind

PHH - April 11th. 2017


Navigation: GUI forms - WPF >

Return to chapter overview

Using the 'VS' Designer 'surface'

PHH - April 11th. 2017


Navigation: GUI forms - WPF >

Return to chapter overview

Some fun with shapes and geometry

PHH - April 11th. 2017


'Third Party' - controls and toolsets

PHH - April 12th. 2017


Graphics - Color, Colors and Brushes etc. ...

PHH - 19th. February 2018.


Navigation: Graphics - Color, Colors and Brushes etc. ... >

Return to chapter overview

Colors

PHH - 19th. February 2018.


Navigation: Graphics - Color, Colors and Brushes etc. ... > Colors >

Return to chapter overview

System.Drawing.Color

PHH - 19th. February 2018.

.


Navigation: Graphics - Color, Colors and Brushes etc. ... > Colors >

Return to chapter overview

System.Windows.Media.Colors

PHH - 19th. February 2018.

.


Navigation: Graphics - Color, Colors and Brushes etc. ... >

Return to chapter overview

Brushes

PHH - 19th. February 2018.


Navigation: »No topics above this level«

Return to chapter overview

Patterns of development

PHH - April 11th. 2017


Navigation: Patterns of development >

Return to chapter overview

Classic Desktop

PHH - April 11th. 2017


Navigation: Patterns of development >

Return to chapter overview

Separation of concerns

PHH - April 11th. 2017


Navigation: Patterns of development >

Return to chapter overview

standard MVVM approach

PHH - April 11th. 2017


Navigation: Patterns of development >

Return to chapter overview

some MVVM frameworks

PHH - April 11th. 2017


Navigation: Patterns of development >

Return to chapter overview

Inversion of control

PHH - April 11th. 2017


Await and Async - doing more than one task ....

private static void Main(string[] args)

{

Console.WriteLine(" Demo 1: Awaiting call to long operation:");

MethodWithAwait().Wait();

Console.WriteLine(" Demo 2: NOT awaiting call to long operation:");

MethodWithoutAwait().Wait();

Console.ReadKey();

}

private static async Task MethodWithAwait()

{

Console.WriteLine(" MethodWithAwait() entered.");

Console.WriteLine(" Awaiting call to LongOperation().");

await LongOperation();

Console.WriteLine(" Pretending to do other work in MethodWithAwait()...");

}

private static async Task MethodWithoutAwait()

{

Console.WriteLine(" MethodWithoutAwait() entered.");

Console.WriteLine(" Call made to LongOperation() with NO await.");

Task task = LongOperation();

Console.WriteLine(" Do some other work in MethodWithoutAwait() after calling LongOperation()...");

}

private static async Task LongOperation()

{

Console.WriteLine(" LongOperation() entered.");

Console.WriteLine(" Starting the long (3 second) process in LongOperation()...");

await Task.Delay(4000);

Console.WriteLine(" Completed the long (3 second) process in LongOperation()...");

}

PHH - 14th. February 2018.


code from sample in codeproject ...

private static void Main(string[] args)

{

Console.WriteLine(" Demo 1: Awaiting call to long operation:");

MethodWithAwait().Wait();

Console.WriteLine(" Demo 2: NOT awaiting call to long operation:");

MethodWithoutAwait().Wait();

Console.ReadKey();

}

private static async Task MethodWithAwait()

{

Console.WriteLine(" MethodWithAwait() entered.");

Console.WriteLine(" Awaiting call to LongOperation().");

await LongOperation();

Console.WriteLine(" Pretending to do other work in MethodWithAwait()...");

}

private static async Task MethodWithoutAwait()

{

Console.WriteLine(" MethodWithoutAwait() entered.");

Console.WriteLine(" Call made to LongOperation() with NO await.");

Task task = LongOperation();

Console.WriteLine(" Do some other work in MethodWithoutAwait() after calling LongOperation()...");

}

private static async Task LongOperation()

{

Console.WriteLine(" LongOperation() entered.");

Console.WriteLine(" Starting the long (3 second) process in LongOperation()...");

await Task.Delay(4000);

Console.WriteLine(" Completed the long (3 second) process in LongOperation()...");

}

PHH - 14th. February 2018.


Navigation: »No topics above this level«

Return to chapter overview

Unit Testing

PHH - April 12th. 2017


using the Visual Studio built-in testing

PHH - April 12th. 2017


'Live' testing - 'Enterprise' VS version, 2017

PHH - April 12th. 2017


Navigation: Unit Testing >

Return to chapter overview

the NUnit framework

PHH - April 12th. 2017


Navigation: »No topics above this level«

Return to chapter overview

Sample applications

PHH - April 11th. 2017


Navigation: Sample applications >

Return to chapter overview

Simple

PHH - April 11th. 2017


Navigation: Sample applications >

Return to chapter overview

More complex

PHH - April 11th. 2017


Navigation: Sample applications >

Return to chapter overview

EF6 - The 'Stock' system

PHH - April 11th. 2017


'Printing and Printers', and Report generation too ...

Before we dive into the world of 'Printing' we need to decide whether we wish to handle the printing

code ourselves in .NET classes, or find a third party tool to do the job for us. Then learn to use the tool.

There is also an in-between way and that is to find out how printing is handled by .NET so we understand

what is going on, but then use the facilities of a well chosen third party tool.

Personally, I have change my ideas about doing everything myself, to the this 'hybrid' approach.

Now that I have started to search for a good reporting (and printing) software tool, the more I look forward

to getting to grips with one, I hesitate to say becoming 'master' of such a tool ;-0)

*** WARNING *** be aware that the latest and best reporting tools will be focused on the latest technologies,

so that WPF will be covered, and that SQL will also figure firmly in the sights of any report generator.

Yes, you should be able to get a WinForms based tool - BUT - big third party companies will have moved on

from WinForms a while ago.

If you are stuck with DBFs for any reason then you may be best to look at a DIY (do it yourself) tool based

on a printing class like mine (described here in this main section on Printing) as well as the data being

presented to the reporting tool in a DataTable format.

DataTables are easy to create from DBF files, as well as being easy to process by our own code - and

slotted straight into a DIY reporter tool as described above.

DIY software tools will tend to be code driven, using suitable printing class DLLs.

Quality third party reporting tools will most likely have well designed GUI front ends.

In British 'vernacular' it is "Horses for Courses", and how much you are prepared to pay for ;-0)

============================================================================

Lets move on to doing some work ..........

Before we can use any defined Printing class in .NET we must first select a printer.

This is simply because the printer driver software for a chosen printer is then used by the underlying

system to provide the canvas coordinates and margins for the surface onto which we must assemble

all our items we wish to print out.

Virtually all printer drivers return different numeric values for the canvas and margins, so there is no

way out - we must select an available printer. Non-physical printers also provide these numerical values.

Below are two such non-physical printers - that for XPS and PDF :-

=========================================================

Selecting an available printer is as true for third party tools as it is for our own DIY ones.

Its just the way Microsoft handles printing - and they may have little choice either.

I suggest you have a good luck at the printer devices installed on your PCs and laptops.

Find those you installed and those common to all Windows 10 systems.

Here is what is on my desktop - I am not sure how some came to be there - part of other software

installs it would seem - check below :-

===================================================================================================================================

For those that don't know, 'Snagit' is a software package / application for capturing screen images (and video) and storing them in a date

sorted Library.

Without 'Snagit' I could hardly manage to create my eNote volumes like this one for example ;-0)

Obviously, Microsoft are responsible for a couple of these useful 'Printer' types.

PDF24 is a free download applet.

A 'Quick' introduction to Printing and Reporting :

Okay guys, lets get going - why do we associate 'Printing' and 'Reporting' ?

Well, its quite simple really.

If we have all of the soft tools available to us so that we can print out any sort of graphical element, then we

can use these soft tools to print out reports as well.

All we need do is prepare the report in a suitable way, such that our code uses the printing tools to display and hard

output the report details.

Let us look at a sample / example image of a report in the Microsoft 'Preview' control from within the WinForm type technology :-

The above image shows different graphical elements on show.

There are lines, and many text elements too.

These elements are created by my code and use methods I have written inside my class library called 'BESTdocument'.

In fact the 'body' of the report uses just three methods, which are quite 'high level'.

By 'high level' I mean that we are using method code which is way above the level of talking with the .NET Framework 'stuff'.

The more we develop our printing class the easier it gets to use the methods to display complex items / elements.

For your information, the reporting code for the above image uses a 'Collection' - a 'List<T>' where 'T' is the 'Person' class.

As well as a Collection being used we also can easily apply LINQ statements and queries.

It is all much easier than it looks.

Summary :

Once we can print a whole variety of graphical elements, then we can form and print out reports too.

If we do things right, printing and reporting can be 'fun'.

Many people fine printing a difficult subject, and for that reason they 'run away' from it.

Yes, many people find printing unpleasant.

Once we get hold (or make) a suitable class library of printing classes (one or three) then we can

tackle printing with success from the very beginning.

Since Alwyn (Jennings-Bramly) introduced me to printing in chapter 15 of his VO text book, I have

never looked back - and even enjoyed the ride !

Be aware that .NET made creating a class library MUCH easier for a number of basic reasons.

Well done the .NET Framework !!!

PHH - April 11th. 2017 & 25th. March 2018.


the 'TestBed' app for printing with the 'BestDocument' Class

Okay guys, we need to get into some detail as to both what the 'TestBed' app for Printing is all about, and

why we need it in the first place, etc., etc..

*** Remember *** we all looking into the task of hand coding documentation to be printed out on a physical

printing device.

By 'Documentation' I simply mean any possible format of the printed page.

So we are talking about invoices, letters, tickets, despatch notes, labels, mail merge 'runs', as well as full

blown data based reports.

Oh! and the "Hello World" message as well ;-0)

Each of these different document formats will use members of the 'BestDocument' (BD) class that I have built to

work on top of the basic .NET functionality for printing. So it is my printing 'Super' class, if you will.

So we could say that the TestBed app is to help us get experience and knowledge of using the BestDocument

class - with all of its overloaded methods. I use it myself, even though I created the BD class.

When we code up a suitable member (usually a method), then we need to get the 'TestBed' to use/run this

method and display on form what we have coded.

What we see displayed will help us make changes and additions to out developing document, before we check

it out once again.

After a few cycles of testing and viewing, we will end up with what we want / need.

It is almost impossible to manually code up documents without having a previewing system to aid us.

Trust me, I know - I have been there and even got the 'T-shirt' ;-0)

Lets take a look at what the supplied 'TestBed' app looks like when we first launch it :-

=================================================================================================

Yes, at first we have no document showing, even though there are six built-in documents for us to study

and try out.

Basically, we can click one of the six buttons, one after another, and in any order we like.

Each individual button causes a document to be created from code and then displayed.

One of the six buttons (Mail Merge) causes a multi page document to be created from data and then

displayed.

Here is one way of displaying the multiple pages - check the selected values in the drop lists :-

========================================================================

And with the same multi-page document displayed, here is page one zoomed

to be larger and more readable :-

========================================================================

Be aware that there are a number of ways the selections can be made and changed.

You will need practise to get the most / best from the selections made.

However, it is good to have lots of options.

Oh! and it is handy to have this option when doing our testing :-

PHH - 17th. March 2018.


Finding and using the available Printers ...

This is a VERY important sub-section.

Although it is a small section, it is VERY important, as without the facilities shown here printing would

be almost impossible.

Why is this ?

Well, the printer driver is a very important and complex piece of software.

Each printer has its own installed 'driver', and so we need to select an available printer before

the driver is then loaded / open, and available for use.

It is the printer driver that supplies information about printing areas, and margins and other boundaries.

Any coordinate system used to 'navigate' around the graphical surface needs numerical details supplied

by the 'driver'.

The great news about .NET is that it makes finding the available printers (and hence their drivers) very

easy indeed.

When printing from VO this was an extremely difficult task - so much so that coder / developers had to

acquire clever bits of software from other colleagues - I never knew how the code worked, but I could

not print without it.

Lets look at a drop box filled with printer names - before we see how to do this in .NET :-

=========================================================================

Yes, quite a few on offer.

So how difficult (or easy) is this to code in the .NET way of doing things?

Basically, the task is just a couple of lines - see below before we discuss :-

===============================================================================================================================

Yes, above we are finding the default printer, as well as finding all the printers available.

The few lines of code also covers filling the combo box with the list of printers.

Looks like the 'PrinterSettings' class is quite a handy class to have to hand.

Since it is a while since I copied this code from help file sections, and then just copied it over

from one of my apps to another, I suggest that you try to code it yourself, and test out the

various parts of it, bit by bit, to get a feel for what is happening.

I also suggest that you get hold of an applet I wrote many years back called "ExaminePrinters".

This applet allows us to do lots with the printers on our system, and to find out their current settings.

The app also allows us to change the settings as well as make test printouts.

There is a section on this app in about 15 sub-sections time.

It looks like this :-

We will discuss the usefulness of the above feature (default settings of paper sizes) in that later section.

Time to move on ............................

PHH - 17th. March & 25th. March 2018.


working code ...

====================================================================================================

=============================================================================================================

===========[ class Form1 ]=======================================================================

//------------------------------------------------------------------------------

// <auto-generated>

// This code was generated by a tool.

// Runtime version: 4.0.30319.42000

// Generator : XSharp.CodeDomProvider 1.1.2.0

// Timestamp : 16/03/2018 14:48:10

//

// Changes to this file may cause incorrect behavior and may be lost if

// the code is regenerated.

// </auto-generated>

//------------------------------------------------------------------------------

BEGIN NAMESPACE XS_TestBedPrinting

PUBLIC PARTIAL CLASS Form1 ;

INHERIT System.Windows.Forms.Form

PRIVATE TestPrint AS System.Windows.Forms.Button

PRIVATE InvoiceQ AS System.Windows.Forms.Button

PRIVATE DocumentC AS System.Windows.Forms.Button

PRIVATE pbMailMerge AS System.Windows.Forms.Button

PRIVATE ImageDoc AS System.Windows.Forms.Button

PRIVATE printPreviewControl1 AS System.Windows.Forms.PrintPreviewControl

PRIVATE MultiGridE AS System.Windows.Forms.Button

PRIVATE cbPrinters AS System.Windows.Forms.ComboBox

PRIVATE cbRows AS System.Windows.Forms.ComboBox

PRIVATE cbColumns AS System.Windows.Forms.ComboBox

PRIVATE label1 AS System.Windows.Forms.Label

PRIVATE label2 AS System.Windows.Forms.Label

PRIVATE label3 AS System.Windows.Forms.Label

PRIVATE cbStartPage AS System.Windows.Forms.ComboBox

PRIVATE cbZoom AS System.Windows.Forms.ComboBox

PRIVATE label4 AS System.Windows.Forms.Label

PRIVATE cbAutoZoom AS System.Windows.Forms.CheckBox

PRIVATE cbResetDrops AS System.Windows.Forms.CheckBox

PRIVATE components := NULL AS System.ComponentModel.IContainer

PROTECTED METHOD Dispose(disposing AS LOGIC) AS VOID

if (disposing .AND. (components != null))

components:Dispose()

endif

Super:Dispose(disposing)

return

PRIVATE METHOD InitializeComponent() AS VOID

SELF:TestPrint := System.Windows.Forms.Button{}

SELF:InvoiceQ := System.Windows.Forms.Button{}

SELF:DocumentC := System.Windows.Forms.Button{}

SELF:pbMailMerge := System.Windows.Forms.Button{}

SELF:ImageDoc := System.Windows.Forms.Button{}

SELF:printPreviewControl1 := System.Windows.Forms.PrintPreviewControl{}

SELF:MultiGridE := System.Windows.Forms.Button{}

SELF:cbPrinters := System.Windows.Forms.ComboBox{}

SELF:cbRows := System.Windows.Forms.ComboBox{}

SELF:cbColumns := System.Windows.Forms.ComboBox{}

SELF:label1 := System.Windows.Forms.Label{}

SELF:label2 := System.Windows.Forms.Label{}

SELF:cbStartPage := System.Windows.Forms.ComboBox{}

SELF:label3 := System.Windows.Forms.Label{}

SELF:cbZoom := System.Windows.Forms.ComboBox{}

SELF:label4 := System.Windows.Forms.Label{}

SELF:cbAutoZoom := System.Windows.Forms.CheckBox{}

SELF:cbResetDrops := System.Windows.Forms.CheckBox{}

SELF:SuspendLayout()

//

// TestPrint

//

SELF:TestPrint:Font := System.Drawing.Font{"Microsoft Sans Serif", 8.25, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((BYTE)(0))}

SELF:TestPrint:Location := System.Drawing.Point{13, 169}

SELF:TestPrint:Name := "TestPrint"

SELF:TestPrint:Size := System.Drawing.Size{110, 23}

SELF:TestPrint:TabIndex := 0

SELF:TestPrint:Text := "Test Print A"

SELF:TestPrint:UseVisualStyleBackColor := true

SELF:TestPrint:Click += System.EventHandler{ SELF, @TestPrint_Click() }

//

// InvoiceQ

//

SELF:InvoiceQ:Font := System.Drawing.Font{"Microsoft Sans Serif", 8.25, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((BYTE)(0))}

SELF:InvoiceQ:Location := System.Drawing.Point{15, 338}

SELF:InvoiceQ:Name := "InvoiceQ"

SELF:InvoiceQ:Size := System.Drawing.Size{108, 23}

SELF:InvoiceQ:TabIndex := 1

SELF:InvoiceQ:Text := "Invoice 'Q'"

SELF:InvoiceQ:UseVisualStyleBackColor := true

SELF:InvoiceQ:Click += System.EventHandler{ SELF, @InvoiceQ_Click() }

//

// DocumentC

//

SELF:DocumentC:Location := System.Drawing.Point{15, 205}

SELF:DocumentC:Name := "DocumentC"

SELF:DocumentC:Size := System.Drawing.Size{108, 23}

SELF:DocumentC:TabIndex := 2

SELF:DocumentC:Text := "Document 'C'"

SELF:DocumentC:UseVisualStyleBackColor := true

SELF:DocumentC:Click += System.EventHandler{ SELF, @DocumentC_Click() }

//

// pbMailMerge

//

SELF:pbMailMerge:Location := System.Drawing.Point{15, 287}

SELF:pbMailMerge:Name := "pbMailMerge"

SELF:pbMailMerge:Size := System.Drawing.Size{108, 23}

SELF:pbMailMerge:TabIndex := 3

SELF:pbMailMerge:Text := "Mail Merge"

SELF:pbMailMerge:TextAlign := System.Drawing.ContentAlignment.BottomCenter

SELF:pbMailMerge:UseVisualStyleBackColor := true

SELF:pbMailMerge:Click += System.EventHandler{ SELF, @pbMailMerge_Click() }

//

// ImageDoc

//

SELF:ImageDoc:Location := System.Drawing.Point{15, 133}

SELF:ImageDoc:Name := "ImageDoc"

SELF:ImageDoc:RightToLeft := System.Windows.Forms.RightToLeft.Yes

SELF:ImageDoc:Size := System.Drawing.Size{108, 23}

SELF:ImageDoc:TabIndex := 4

SELF:ImageDoc:Text := "Image Doc"

SELF:ImageDoc:UseVisualStyleBackColor := true

SELF:ImageDoc:Click += System.EventHandler{ SELF, @ImageDoc_Click() }

//

// printPreviewControl1

//

SELF:printPreviewControl1:Location := System.Drawing.Point{190, 12}

SELF:printPreviewControl1:Name := "printPreviewControl1"

SELF:printPreviewControl1:Size := System.Drawing.Size{409, 366}

SELF:printPreviewControl1:TabIndex := 5

//

// MultiGridE

//

SELF:MultiGridE:Location := System.Drawing.Point{15, 245}

SELF:MultiGridE:Name := "MultiGridE"

SELF:MultiGridE:Size := System.Drawing.Size{108, 23}

SELF:MultiGridE:TabIndex := 6

SELF:MultiGridE:Text := "Multi-Grid"

SELF:MultiGridE:UseVisualStyleBackColor := true

SELF:MultiGridE:Click += System.EventHandler{ SELF, @MultiGridE_Click() }

//

// cbPrinters

//

SELF:cbPrinters:FormattingEnabled := true

SELF:cbPrinters:Location := System.Drawing.Point{2, 12}

SELF:cbPrinters:Name := "cbPrinters"

SELF:cbPrinters:Size := System.Drawing.Size{182, 21}

SELF:cbPrinters:TabIndex := 7

SELF:cbPrinters:SelectedIndexChanged += System.EventHandler{ SELF, @cbPrinters_SelectedIndexChanged() }

//

// cbRows

//

SELF:cbRows:FormattingEnabled := true

SELF:cbRows:Location := System.Drawing.Point{190, 401}

SELF:cbRows:Name := "cbRows"

SELF:cbRows:Size := System.Drawing.Size{60, 21}

SELF:cbRows:TabIndex := 8

SELF:cbRows:SelectionChangeCommitted += System.EventHandler{ SELF, @cbRows_SelectionChangeCommitted() }

//

// cbColumns

//

SELF:cbColumns:FormattingEnabled := true

SELF:cbColumns:Location := System.Drawing.Point{267, 401}

SELF:cbColumns:Name := "cbColumns"

SELF:cbColumns:Size := System.Drawing.Size{60, 21}

SELF:cbColumns:TabIndex := 9

SELF:cbColumns:SelectionChangeCommitted += System.EventHandler{ SELF, @cbColumns_SelectionChangeCommitted() }

//

// label1

//

SELF:label1:AutoSize := true

SELF:label1:Location := System.Drawing.Point{188, 384}

SELF:label1:Name := "label1"

SELF:label1:Size := System.Drawing.Size{46, 13}

SELF:label1:TabIndex := 10

SELF:label1:Text := "Rows :"

//

// label2

//

SELF:label2:AutoSize := true

SELF:label2:Location := System.Drawing.Point{265, 383}

SELF:label2:Name := "label2"

SELF:label2:Size := System.Drawing.Size{62, 13}

SELF:label2:TabIndex := 11

SELF:label2:Text := "Columns :"

//

// cbStartPage

//

SELF:cbStartPage:FormattingEnabled := true

SELF:cbStartPage:Location := System.Drawing.Point{346, 401}

SELF:cbStartPage:Name := "cbStartPage"

SELF:cbStartPage:Size := System.Drawing.Size{57, 21}

SELF:cbStartPage:TabIndex := 12

SELF:cbStartPage:SelectionChangeCommitted += System.EventHandler{ SELF, @cbStartPage_SelectionChangeCommitted() }

//

// label3

//

SELF:label3:AutoSize := true

SELF:label3:Location := System.Drawing.Point{345, 383}

SELF:label3:Name := "label3"

SELF:label3:Size := System.Drawing.Size{42, 13}

SELF:label3:TabIndex := 13

SELF:label3:Text := "Start :"

//

// cbZoom

//

SELF:cbZoom:FormattingEnabled := true

SELF:cbZoom:Location := System.Drawing.Point{424, 401}

SELF:cbZoom:Name := "cbZoom"

SELF:cbZoom:Size := System.Drawing.Size{59, 21}

SELF:cbZoom:TabIndex := 14

SELF:cbZoom:SelectionChangeCommitted += System.EventHandler{ SELF, @cbZoom_SelectionChangeCommitted() }

//

// label4

//

SELF:label4:AutoSize := true

SELF:label4:Location := System.Drawing.Point{423, 383}

SELF:label4:Name := "label4"

SELF:label4:Size := System.Drawing.Size{46, 13}

SELF:label4:TabIndex := 15

SELF:label4:Text := "Zoom :"

//

// cbAutoZoom

//

SELF:cbAutoZoom:AutoSize := true

SELF:cbAutoZoom:Location := System.Drawing.Point{504, 401}

SELF:cbAutoZoom:Name := "cbAutoZoom"

SELF:cbAutoZoom:Size := System.Drawing.Size{95, 17}

SELF:cbAutoZoom:TabIndex := 16

SELF:cbAutoZoom:Text := "Auto Zoom :"

SELF:cbAutoZoom:UseVisualStyleBackColor := true

SELF:cbAutoZoom:CheckStateChanged += System.EventHandler{ SELF, @cbAutoZoom_CheckStateChanged() }

//

// cbResetDrops

//

SELF:cbResetDrops:AutoSize := true

SELF:cbResetDrops:Location := System.Drawing.Point{19, 391}

SELF:cbResetDrops:Name := "cbResetDrops"

SELF:cbResetDrops:Size := System.Drawing.Size{104, 17}

SELF:cbResetDrops:TabIndex := 17

SELF:cbResetDrops:Text := "Reset Drops :"

SELF:cbResetDrops:UseVisualStyleBackColor := true

SELF:cbResetDrops:CheckStateChanged += System.EventHandler{ SELF, @cbResetDrops_CheckStateChanged() }

//

// Form1

//

SELF:AutoScaleDimensions := System.Drawing.SizeF{7, 13}

SELF:AutoScaleMode := System.Windows.Forms.AutoScaleMode.Font

SELF:ClientSize := System.Drawing.Size{611, 427}

SELF:Controls:Add(SELF:cbResetDrops)

SELF:Controls:Add(SELF:cbAutoZoom)

SELF:Controls:Add(SELF:label4)

SELF:Controls:Add(SELF:cbZoom)

SELF:Controls:Add(SELF:label3)

SELF:Controls:Add(SELF:cbStartPage)

SELF:Controls:Add(SELF:label2)

SELF:Controls:Add(SELF:label1)

SELF:Controls:Add(SELF:cbColumns)

SELF:Controls:Add(SELF:cbRows)

SELF:Controls:Add(SELF:cbPrinters)

SELF:Controls:Add(SELF:MultiGridE)

SELF:Controls:Add(SELF:printPreviewControl1)

SELF:Controls:Add(SELF:ImageDoc)

SELF:Controls:Add(SELF:pbMailMerge)

SELF:Controls:Add(SELF:DocumentC)

SELF:Controls:Add(SELF:InvoiceQ)

SELF:Controls:Add(SELF:TestPrint)

SELF:Font := System.Drawing.Font{"Microsoft Sans Serif", 8.25, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((BYTE)(0))}

SELF:Name := "Form1"

SELF:Text := "Our own Printing :"

SELF:ResumeLayout(false)

SELF:PerformLayout()

END CLASS

END NAMESPACE

=====================================================================================

USING System

USING System.Collections.Generic

USING System.ComponentModel

USING System.Data

USING System.Drawing

USING System.Linq

USING System.Text

using System.Threading.Tasks

using System.Windows.Forms

using ClassLibraryBESTdocument

using System.Drawing.Printing

using System.IO

BEGIN NAMESPACE XS_TestBedPrinting

PUBLIC PARTIAL CLASS Form1 ;

INHERIT System.Windows.Forms.Form

PUBLIC oMM AS MailMerge

PUBLIC oMMDT AS System.Data.DataTable

PUBLIC cFocusXML AS STRING

PUBLIC cFocusXSD AS STRING

PUBLIC Landscape AS LOGIC

PUBLIC PrintDirect AS LOGIC

PUBLIC cWhich AS STRING

PUBLIC nRowPointer AS INT

PUBLIC NumberOfRows AS INT

PUBLIC NumberOfColumns AS INT

PUBLIC oDCC AS System.Data.DataColumnCollection

PUBLIC oDRC AS System.Data.DataRowCollection

PUBLIC cPrinterName AS STRING

PUBLIC cDefaultPrinterName AS STRING

PUBLIC oBD AS ClassLibraryBESTdocument.BestDocument

PUBLIC Preview := true AS LOGIC

PUBLIC oPreviewDlg AS System.Windows.Forms.PrintPreviewDialog

PUBLIC CONSTRUCTOR()

InitializeComponent()

//------------------------------

self:MinimumSize := self:size

self:MaximumSize := self:size

//------------------------------

MyInitialise()

return

PUBLIC METHOD MyInitialise() AS VOID

self:cPrinterName := ""

//self:printPreviewControl1:Columns := 2

//self:printPreviewControl1:Rows := 2

FindPrinters()

FillRowColDrops()

ResetRowColDrops()

return

PUBLIC METHOD FillRowColDrops() AS VOID

SELF:cbRows:Items:Clear()

SELF:cbRows:Items:AddRange(<object>{1,2,3,4,5,6})

/*local oIntRange as object[]

oIntRange := <object>{1,2,3,4,5,6}

SELF:cbRows:Items:AddRange(oIntRange)

*/

SELF:cbRows:SelectedIndex := 0

//------------------------------------------------

SELF:cbColumns:Items:Clear()

SELF:cbColumns:Items:AddRange(<object>{1,2,3,4,5,6})

SELF:cbColumns:SelectedIndex := 0

//------------------------------------------------

SELF:cbStartPage:Items:Clear()

SELF:cbStartPage:Items:AddRange(<object>{1,2,3})

SELF:cbStartPage:SelectedIndex := 0

//------------------------------------------------

SELF:cbZoom:Items:Clear()

SELF:cbZoom:Items:AddRange(<object>{0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1.0,1.2,1.4,1.6,2.0,2.5,3.0})

SELF:cbZoom:SelectedIndex := 4

return

PUBLIC METHOD FindPrinters() AS VOID

//================ find and set printer(s) =================

LOCAL cDefaultPrinter AS STRING

LOCAL oPS AS PrinterSettings

oPS := PrinterSettings{}

cDefaultPrinter := oPS:PrinterName

SELF:cPrinterName := cDefaultPrinter

self:cDefaultPrinterName := oPS:PrinterName

self:Text := "Test Bed Printing : BestDocument class : "+"[ printer >> "+SELF:cPrinterName+" ]"

LOCAL acPrinters AS STRING[]

LOCAL oPSSC AS PrinterSettings.StringCollection

oPSSC := PrinterSettings.InstalledPrinters

acPrinters := STRING[]{oPSSC:Count}

oPSSC:CopyTo(acPrinters,0)

SELF:cbPrinters:Items:Clear()

SELF:cbPrinters:Items:AddRange(acPrinters)

SELF:cbPrinters:SelectedItem := cDefaultPrinter

//self:cDefaultPrinterName := oPS:PrinterName

//self:cPrinterName := self:cDefaultPrinterName

RETURN

PUBLIC METHOD ResetRowColDrops() AS VOID

SELF:cbRows:SelectedIndex := 0

SELF:cbColumns:SelectedIndex := 0

SELF:cbStartPage:SelectedIndex := 0

//---------------------------------

self:printPreviewControl1:Columns := 1

self:printPreviewControl1:Rows := 1

self:printPreviewControl1:StartPage := 1

SELF:cbAutoZoom:Checked := FALSE

self:printPreviewControl1:Zoom := 0.4

return

PUBLIC METHOD MyTestDocument(o AS OBJECT, e AS System.Drawing.Printing.PrintPageEventArgs) AS VOID

LOCAL oFont10A, oFont12A AS Font

LOCAL oFont12Ab, oFont9A, oFont16A, oFont22Ab AS Font

oBD:SetGraphics(e:Graphics)

//==========================================

oFont22Ab := Font{"Arial",22,FontStyle.Bold}

oFont16A := Font{"Arial",16}

oFont12A := Font{"Arial",12}

oFont12Ab := Font{"Arial",12,FontStyle.Bold}

oFont10A := Font{"Arial",10}

oFont9A := Font{"Arial",9}

//------------------------------------------

oBD:Font := oFont10A

//----------------- Headings ------------------

LOCAL oPPC1, oPPC2, oPPC AS PaperCoords

LOCAL cHead AS STRING

oPPC1 := PaperCoords{1, 2}

oPPC2 := PaperCoords{1, 98}

cHead := "Testing Report Printing : "

oBD:TEXT(oPPC1, cHead)

LOCAL oDT AS DateTime

oDT := DateTime.TODAY

cHead := "Printed on :- "+oDT:ToLongDateString()

oBD:TextRight(oPPC2, cHead)

oPPC1 := PaperCoords{5, 2}

oPPC2 := PaperCoords{5, 98}

oBD:DrawLine(oPPC1, oPPC2, 1)

oPPC1 := PaperCoords{96, 2}

oPPC2 := PaperCoords{96, 98}

oBD:DrawLine(oPPC1, oPPC2, 1)

//------------------- Footings --------------------

oPPC1 := PaperCoords{98, 98}

oBD:TextRight(oPPC1, "Page : 1 of 1")

//------------------- Body -------------------

oBD:Font := oFont22Ab

oPPC := PaperCoords{20,15}

oBD:TEXT(oPPC, "Thanks to Michael Rubinstein !!!")

oPPC := PaperCoords{30,20}

oBD:TEXT(oPPC, "For supplying the 'IPicture' library.")

oBD:Color := Color.CadetBlue

oBD:ReSetBrushAndPen()

oPPC := PaperCoords{40,25}

oBD:TEXT(oPPC, "It has helped me do a lot.")

oBD:Color := Color.RosyBrown

oBD:ReSetBrushAndPen()

oPPC := PaperCoords{50,30}

oBD:TEXT(oPPC, "We printed signatures on letters.")

oBD:Color := Color.Purple

oBD:ReSetBrushAndPen()

oPPC := PaperCoords{60,35}

oBD:TEXT(oPPC, "And images too !")

oPPC := PaperCoords{70,40}

oBD:BitmapPlace(oPPC ,"..\..\Images\Qmk2.jpg")

//--- I get quuite confused with the above string path ---

//--- BUT, it seems to work OK ! ---

//oBD:BitmapPlace(oPPC ,"C:/TempXS/Qmk2.jpg")

//---------- keep this for a single page ---------

e:HasMorePages := FALSE

return

PUBLIC METHOD MyTestDocumentQ(o AS OBJECT, e AS System.Drawing.Printing.PrintPageEventArgs) AS VOID

LOCAL oCenturyFontVL, oCenturyFontM, oScriptFont, oTimes12 AS Font

LOCAL oLucidaMediumFont, oLucidaLargeFont, oLucidaFont AS Font

LOCAL oTimesFont12b, oArialBold13, oMSSS10, oTimes12BI AS Font

oCenturyFontVL := Font{"Century Schoolbook",80}

oCenturyFontM := Font{"Century Schoolbook",30}

oScriptFont := Font{"Script MT Bold",12}

oLucidaFont := Font{"Lucida Sans",14}

oLucidaMediumFont := Font{"Lucida Sans",20}

oLucidaLargeFont := Font{"Lucida Sans",36}

oTimesFont12b := Font{"Times New Roman",12,FontStyle.Bold}

oTimes12 := Font{"Times New Roman",12}

oArialBold13 := Font{"Arial",13, FontStyle.Bold}

oMSSS10 := Font{"Microsoft Sans Serif",10}

oBD:SetGraphics( e:Graphics)

oBD:JobName := "TestInvoice"

oBD:ResetLinesPerPage(67)

/*

oFont12Ab := Font{"Arial",12,FontStyle.Bold}

*/

//------------------- Logo ------------------------

LOCAL cText AS STRING

LOCAL oPPC AS PaperCoords

LOCAL nQd, nQa AS Double

nQd := 0.5

nQa := 75.0

oPPC := PaperCoords{nQd,nQa}

oBD:Font := oCenturyFontVL

oBD:Text(oPPC, "Q")

nQd += 3.5

nQa += 5.3

oPPC := PaperCoords{nQd,nQa}

oBD:Font := oCenturyFontM

oBD:Text(oPPC, "e")

nQd += 8.2

nQa -= 7.5

oPPC := PaperCoords{nQd,nQa}

oBD:Font := oScriptFont

oBD:Text(oPPC, e"\"Eminence thro' Quality\"")

//------------------ Company ------------------------

LOCAL nBd, nBa AS Double

nBd := 1.0

nBa := 1.0

oPPC := PaperCoords{nBd,nBa}

oBD:Font := oLucidaMediumFont

oBD:Text(oPPC, "INVOICE")

LOCAL nD, nA AS Double

nD := nBd+2.5

nA := nBa-1.0

oPPC := PaperCoords{nD,nA}

oBD:Font := oLucidaLargeFont

oBD:Text(oPPC, "BEST consultancy")

nD := nBd+7.9

nA := nBa-0.0

oPPC := PaperCoords{nD,nA}

oBD:Font := oTimesFont12b

cText := "Specialists in all aspects of Business & Personal Computing"

oBD:Text(oPPC, cText)

nD := nBd+10.5

nA := nBa+0.0

oPPC := PaperCoords{nD,nA}

oBD:Font := oLucidaFont

oBD:Text(oPPC, "V.A.T. registration no. :- GB 648 4766 89")

//---------------------------- Do Grid ----------------------------

nD := nBd+41.5

nA := nBa+1.0

oPPC := PaperCoords{nD,nA}

LOCAL aX, aY AS Double[]

LOCAL nE, d1, d2, d3 AS Double

nE := 3.0

d1 := 13.5

d2 := 67

d3 := 11.5

aX := <Double>{d1,d2,d3}

aY := <Double>{nE,nE,nE,nE,nE,nE,nE,nE,nE}

//oBD:GridComplex(oPPC, aX , aY)

LOCAL nXg, nYg AS Double

LOCAL oPPC3 AS PaperCoords

//----------- left column -----------------

oPPC := PaperCoords{nD,nA}

nXg := nA+d1

nYg := nD+9*nE

oPPC3 := PaperCoords{nYg, nXg}

oBD:GridRegularNbyM(oPPC,oPPC3,9,1)

//---------- middle column ----------------

oPPC := PaperCoords{nD,nA+d1}

nXg := nA+d1+d2

oPPC3 := PaperCoords{nYg, nXg}

oBD:GridRegularNbyM(oPPC,oPPC3,9,1)

//------------ right column ---------------

oPPC := PaperCoords{nD,nA+d1+d2}

nXg := nA+d1+d2+d3

oPPC3 := PaperCoords{nYg, nXg}

oBD:GridRegularNbyM(oPPC,oPPC3,9,1)

//---------------------------------

//-------------- totals ---------------

nXg := nA+d1+d2

nYg := nD+9*nE

oPPC := PaperCoords{nYg, nXg}

nXg += d3

nYg += 3*nE

oPPC3 := PaperCoords{nYg, nXg}

oBD:GridRegularNbyM(oPPC,oPPC3,3,1)

//-------------------------------------

oBD:Font := oLucidaFont

LOCAL aNA AS STRING[]

LOCAL nM AS Double

aNA := STRING[]{6}

aNA[1] := "Dr. Philip H. Hepburn,"

aNA[2] := "BEST house,"

aNA[3] := "19, Firbank Crescent,"

aNA[4] := "Newport, S. Wales."

aNA[5] := "NP19 7QR"

//nM := 22

nM := 20

oPPC := PaperCoords{nM,61}

oBD:Block(oPPC, aNA)

//--------------------------------

oBD:Font := oMSSS10

LOCAL aNC AS STRING[]

aNC := STRING[]{6}

aNC[1] := "tele : (01633) 243057"

aNC[2] := "email : phil@phhepburn.f2s.com"

aNC[3] := "web1 : www.BESTconsultancy.co.uk"

aNC[4] := "web2 : www.BestPrimaryLibrary.com"

aNC[5] := "web3 : www.ClickStartVulcan.net"

oPPC := PaperCoords{nM+11,61.2}

oBD:Block(oPPC, aNC)

//--------------- Col Headers -------------------

oBD:Font := oArialBold13

oPPC := PaperCoords{40,4}

oBD:Text(oPPC,"Date")

oPPC := PaperCoords{40,43}

oBD:Text(oPPC,"Particulars")

oPPC := PaperCoords{40,82}

oBD:Text(oPPC,"Amount £")

//-------------------------

LOCAL oFSbi AS FontStyle

oFSbi := FontStyle.Bold | FontStyle.Italic

oTimes12BI := Font{"Times New Roman",12,oFSbi}

oBD:Font := oTimes12bi

oPPC := PaperCoords{73,2}

cText := e"Cheques should be made payable to \"BEST consultancy\""

oBD:Text(oPPC, cText)

//-----------------------

//-------- totals -------

oBD:Font := oArialBold13

LOCAL nT AS Double

nT := 70.5

oPPC := PaperCoords{nT,75}

oBD:Text(oPPC,"Cost")

oPPC := PaperCoords{nT+nE,75}

oBD:Text(oPPC,"V.A.T.")

oPPC := PaperCoords{nT+2*nE,75}

oBD:Text(oPPC,"Total")

//------- bottom left -------

nT := 82

oPPC := PaperCoords{nT,2}

oBD:Text(oPPC,"Conditions of Trade :-")

oPPC := PaperCoords{nT+11,2}

oBD:Text(oPPC,"V.A.T. Registration No. GB 648 4766 89")

oBD:Font := oTimes12

LOCAL aC AS STRING[]

oPPC := PaperCoords{nT+3,4}

aC := STRING[]{4}

aC[1] := "Please note that payment is due within 14 days of the invoice date."

aC[2] := "The surcharge of 5% may be charged for any overdue account."

aC[3] := e"All goods remain the property of \"BEST consultancy\" until paid for in full."

oBD:Block(oPPC,aC)

//-------------------------------------------

oBD:Font := oLucidaFont

nD := nBd+15.5

nA := nBa

oPPC := PaperCoords{nD,nA}

oBD:Text(oPPC,"INVOICE date :-")

nA := nBa+48.5

oPPC := PaperCoords{nD,nA}

oBD:Text(oPPC,"INVOICE no. :-")

//-------------------------------

oBD:Font := oArialBold13

oPPC := PaperCoords{nM,nA}

oBD:Text(oPPC,"From :")

oPPC := PaperCoords{nM,nBd}

oBD:Text(oPPC,"To :")

SELF:DisplayOrderDetails(oBD,nM,nD,nD+9*nE)

//---------- keep this for a single page ---------

e:HasMorePages := FALSE

return

PUBLIC METHOD DisplayOrderDetails(oBD AS ClassLibraryBESTdocument.BestDocument, nC AS REAL8, nI AS REAL8, nG AS REAL8) AS VOID

LOCAL aCompany AS STRING[]

aCompany := STRING[]{8}

aCompany[1] := "Mr. Sven Gonaway,"

aCompany[2] := "Storkland Healthcare Ld.,"

aCompany[3] := "Pentagle Way,"

aCompany[4] := "Cardiff Business Park,"

aCompany[5] := "Llanishen,"

aCompany[6] := "CF13 4ER"

aCompany[7] := "(012920) 472200"

aCompany[8] := "sven.gona@wasthere.net"

LOCAL oMSSS12 AS Font

oMSSS12 := Font{"Microsoft Sans Serif",12}

oBD:Font := oMSSS12

LOCAL oPPC AS PaperCoords

oPPC := PaperCoords{nC,8}

oBD:Block(oPPC,aCompany)

//----------------------------------------

oPPC := PaperCoords{nI,23}

oBD:Text(oPPC,"13/03/2005")

oPPC := PaperCoords{nI,71}

oBD:Text(oPPC,"2526")

//----------------------------------------

LOCAL aParts AS STRING[]

aParts := STRING[]{8}

aParts[1] := "Supply and deliver computer & IT parts as listed below :-"

aParts[2] := "replacement keyboard and laser mouse with fancy mat"

aParts[3] := "new HDD and IDE cable, DVD/CD drive"

aParts[4] := "refurbished flat screen 23 inch monitor and P/S"

aParts[5] := "install, test, & integration of all hardware provided"

aParts[6] := "software services for Vulcan beginners classes"

aParts[7] := "dotNet master class and room sweep for management"

aParts[8] := "empty the trash cans in main offices on the way out"

oPPC := PaperCoords{nG-0.5,18}

oBD:BlockRestrictRHS(oPPC,aParts,83,1.63)

//--------------------------------------------------------------------

LOCAL aDates AS STRING[]

aDates := STRING[]{3}

aDates[1] := "September"

aDates[2] := "22nd."

aDates[3] := "2008"

oPPC := PaperCoords{nG-0.5,3}

oBD:BlockRightRestrictRHS(oPPC,aDates,15,1.63)

//----------------------------------------

LOCAL aCosts AS STRING[]

aCosts := STRING[]{8}

aCosts[1] := ""

aCosts[2] := "27.13"

aCosts[3] := "92.67"

aCosts[4] := "87.34"

aCosts[5] := "25.00"

aCosts[6] := "177.00"

aCosts[7] := "0.02"

aCosts[8] := "10.10"

oPPC := PaperCoords{nG-0.5,84}

oBD:BlockDecimal(oPPC,aCosts,2,9,1.63)

//----------------------------------------

LOCAL aTotals AS STRING[]

aTotals := STRING[]{3}

aTotals[1] := "419.26"

aTotals[2] := "73.37"

aTotals[3] := "492.63"

oPPC := PaperCoords{nG+26.5,84}

oBD:BlockDecimal(oPPC,aTotals,2,9,1.63)

//----------------------------------------

return

PUBLIC METHOD MyTestDocumentC(o AS OBJECT, e AS System.Drawing.Printing.PrintPageEventArgs) AS VOID

LOCAL oFont10A, oFont12A AS Font

LOCAL oFont12Ab, oFont8A, oFont16A, oFont22Ab, oFont16Ab AS Font

oBD:SetGraphics(e:Graphics)

//==========================================

//oGraphics := e:Graphics

//oBD:SetGraphics(oGraphics)

oFont22Ab := Font{"Arial",22,FontStyle.Bold}

oFont16A := Font{"Arial",16}

oFont16Ab := Font{"Arial",16,FontStyle.Bold}

oFont12A := Font{"Arial",12}

oFont12Ab := Font{"Arial",12,FontStyle.Bold}

oFont10A := Font{"Arial",10}

oFont8A := Font{"Arial",8}

//------------------------------------------------

LOCAL oGC1, oGC2 AS GraphCoords

LOCAL cText AS STRING

oBD:Color := Color.LightGray

oBD:ReSetBrushAndPen()

oBD:Pen := Pen{oBD:Brush, 0.5}

oGC1 := GraphCoords{5,95}

oGC2 := GraphCoords{95,5}

oBD:GridRegularNbyM(oGC1,oGC2,100,100)

//------------------------------------

oBD:Color := Color.Black

oBD:ReSetBrushAndPen()

oBD:Pen := Pen{oBD:Brush, 2.0}

oGC1 := GraphCoords{5,95}

oGC2 := GraphCoords{95,5}

oBD:GridRegularNbyM(oGC1,oGC2,10,10)

//------------------------------------

oGC1 := GraphCoords{45,3}

oBD:Color := Color.Blue

oBD:ReSetBrushAndPen()

cText := "X - axis"

oBD:Font := oFont22Ab

oBD:Text(oGC1, cText)

oGC1 := GraphCoords{1,55}

oBD:Color := Color.Red

oBD:ReSetBrushAndPen()

cText := "Y - axis"

oBD:TextUp(oGC1, cText)

oBD:Color := Color.CadetBlue

oBD:ReSetBrushAndPen()

oGC1 := GraphCoords{35,100}

cText := "Click Start - Graph Paper ?"

oBD:Text(oGC1, cText)

//---------- keep this for a single page ---------

e:HasMorePages := FALSE

return

PUBLIC METHOD MyTestDocumentA(o AS OBJECT, e AS System.Drawing.Printing.PrintPageEventArgs) AS VOID

oBD:SetGraphics(e:Graphics)

LOCAL oFont22Ab AS Font

oFont22Ab := Font{"Arial",22,FontStyle.Bold}

oBD:Font := oFont22Ab

//------------------------------------------------

LOCAL oPPC, oPPC1, oPPC2 AS PaperCoords

oPPC := PaperCoords{13,13}

oBD:TextCenterPage(oPPC, "[ Is this at Page center ? ]")

oBD:ApplyNewColor(Color.Green)

oPPC1 := PaperCoords{17,20}

oPPC2 := PaperCoords{17,80}

oBD:TextCenterBetween(oPPC1, oPPC2, "[ center between 20 / 80 ?? ]")

oBD:ApplyNewColor(Color.Chocolate)

oPPC1 := PaperCoords{21,30}

oPPC2 := PaperCoords{21,70}

oBD:TextCenterBetween(oPPC1, oPPC2, "[ now 30 & 70 !! ]")

oBD:ApplyNewColor(Color.Purple)

oPPC := PaperCoords{25,13}

oBD:TextCenterPageUnder(oPPC, "< underline center page >")

//--------------------------------------------------------

oBD:ApplyNewColor(Color.RosyBrown)

oPPC := PaperCoords{1,1}

oBD:TextUnder(oPPC, "Underlined Text :")

oBD:ApplyNewColor(Color.Blue)

oPPC := PaperCoords{26,94}

oBD:TextUp(oPPC, "Upstanding Text")

//--------------------------------------

LOCAL nChop AS Double

nChop := 69

oBD:ApplyNewColor(Color.CadetBlue)

oPPC := PaperCoords{39,1}

oBD:TextChop(oPPC, "This is my first line of text",nChop)

oPPC := PaperCoords{43,1}

oBD:TextChop(oPPC, "to demonstrate the chopping",nChop)

oPPC := PaperCoords{47,1}

oBD:TextChop(oPPC, "of characters which would otherwise",nChop)

//----------------------------------------------------------

nChop := 39

oBD:ApplyNewColor(Color.Black)

oPPC := PaperCoords{39,1}

oBD:TextChop(oPPC, "This is my first line of text",nChop)

oPPC := PaperCoords{43,1}

oBD:TextChop(oPPC, "to demonstrate the chopping",nChop)

oPPC := PaperCoords{47,1}

oBD:TextChop(oPPC, "of characters which would otherwise",nChop)

//-------------------------------------------------------------

oBD:ApplyNewColor(Color.Black)

oPPC1 := PaperCoords{59,50}

oPPC2 := PaperCoords{75,50}

oBD:DrawLine(oPPC1, oPPC2, 2.5)

oBD:ApplyNewColor(Color.Red)

oPPC1 := PaperCoords{59,80}

oPPC2 := PaperCoords{75,80}

oBD:DrawLine(oPPC1, oPPC2, 4.5)

oBD:ApplyNewColor(Color.BlueViolet)

oPPC1 := PaperCoords{59,50}

oPPC2 := PaperCoords{59,80}

oBD:TextCenterBetween(oPPC1, oPPC2, "will this work")

oPPC1 := PaperCoords{63,50}

oPPC2 := PaperCoords{63,80}

oBD:TextCenterBetween(oPPC1, oPPC2, "and have")

oPPC1 := PaperCoords{67,50}

oPPC2 := PaperCoords{67,80}

oBD:TextCenterBetween(oPPC1, oPPC2, "a few words on")

oPPC1 := PaperCoords{71,50}

oPPC2 := PaperCoords{71,80}

oBD:TextCenterBetween(oPPC1, oPPC2, "display ...")

//---------- keep this for a single page ---------

e:HasMorePages := FALSE

return

PUBLIC METHOD MyTestDocumentE(o AS OBJECT, e AS System.Drawing.Printing.PrintPageEventArgs) AS VOID

LOCAL oFont10A, oFont12A AS Font

LOCAL oFont12Ab, oFont8A, oFont16A, oFont22Ab AS Font

oBD:SetGraphics(e:Graphics)

oBD:ResetLinesPerPage(67)

oFont22Ab := Font{"Arial",22,FontStyle.Bold}

oFont16A := Font{"Arial",16}

oFont12A := Font{"Arial",12}

oFont12Ab := Font{"Arial",12,FontStyle.Bold}

oFont10A := Font{"Arial",10}

oFont8A := Font{"Arial",8}

//---------------------------------------------------

//============ place your test code here ============

LOCAL oPGC1 AS PageCoords

oPGC1 := PageCoords{10,10}

oBD:GridComplex(oPGC1,<Double>{3,5,7,9,11,9,7,5,3},<Double>{1,3,5,7,5,3,2,3,5,7,5,3,1})

//=============== end of test code ==================

//------------ keep this for a single page -----------

e:HasMorePages := FALSE

RETURN

PUBLIC METHOD ShowDocumentA() AS VOID

//MessageBox.Show("show doc ...","In Show :")

//self:oBD := BestDocument{"TestShow","OKI C5600 linksys"}

self:oBD := BestDocument{"TestShow",self:cPrinterName}

self:oBD:ResetOrientation("P")

//self:oBD:ResetOrientation("L")

self:oBD:SetPageBoundaries()

self:ResetRowColDrops()

//=======================================================================

self:oBD:PrintPage += PrintPageEventHandler{self, @MyTestDocumentA()}

if !self:Preview

//oBD:Print()

else

self:printPreviewControl1:Document := self:oBD

self:printPreviewControl1:UseAntiAlias := true

self:printPreviewControl1:Document:DocumentName := "Second Test :"

self:oPreviewDlg := PrintPreviewDialog{}

self:oPreviewDlg:Document := self:oBD

self:oPreviewDlg:ShowDialog()

endif

self:oBD:PrintPage -= PrintPageEventHandler{self, @MyTestDocumentA()}

//=======================================================================

self:oBD:Dispose()

self:oBD := null_object

return

// ----------------------------------------

PUBLIC METHOD ShowDocumentQ() AS VOID

//MessageBox.Show("show doc ...","In Show :")

//self:oBD := BestDocument{"TestShow","OKI C5600 linksys"}

self:oBD := BestDocument{"TestShow",self:cPrinterName}

//self:oBD := BestDocument{"TestShow",""}

self:oBD:ResetOrientation("P")

//self:oBD:ResetOrientation("L")

self:oBD:SetPageBoundaries()

self:ResetRowColDrops()

//=======================================================================

self:oBD:PrintPage += PrintPageEventHandler{self, @MyTestDocumentQ()}

if !self:Preview

//oBD:Print()

else

self:printPreviewControl1:Document := self:oBD

self:printPreviewControl1:UseAntiAlias := true

self:printPreviewControl1:Document:DocumentName := "Fourth Test :"

self:oPreviewDlg := PrintPreviewDialog{}

self:oPreviewDlg:Document := self:oBD

self:oPreviewDlg:ShowDialog()

endif

self:oBD:PrintPage -= PrintPageEventHandler{self, @MyTestDocumentQ()}

//=======================================================================

self:oBD:Dispose()

self:oBD := null_object

return

PUBLIC METHOD ShowDocumentC() AS VOID

//MessageBox.Show("printer name ...",self:cPrinterName+":")

//self:oBD := BestDocument{"TestShow","OKI C5600 linksys"}

self:oBD := BestDocument{"TestShow",self:cPrinterName}

//self:oBD := BestDocument{"TestShow",""}

//self:oBD:ResetOrientation("P")

self:oBD:ResetOrientation("L")

self:oBD:SetPageBoundaries()

self:ResetRowColDrops()

//=======================================================================

self:oBD:PrintPage += PrintPageEventHandler{self, @MyTestDocumentC()}

if !self:Preview

//oBD:Print()

else

self:printPreviewControl1:Document := self:oBD

self:printPreviewControl1:UseAntiAlias := true

self:printPreviewControl1:Document:DocumentName := "Third Test :"

self:oPreviewDlg := PrintPreviewDialog{}

self:oPreviewDlg:Document := self:oBD

self:oPreviewDlg:ShowDialog()

endif

self:oBD:PrintPage -= PrintPageEventHandler{self, @MyTestDocumentC()}

//=======================================================================

self:oBD:Dispose()

self:oBD := null_object

return

PUBLIC METHOD ShowDocument() AS VOID

//MessageBox.Show("printer name ...",self:cPrinterName+":")

//self:oBD := BestDocument{"TestShow","OKI C5600 linksys"}

self:oBD := BestDocument{"TestShow",self:cPrinterName}

//self:oBD := BestDocument{"TestShow",""}

self:oBD:ResetOrientation("P")

//self:oBD:ResetOrientation("L")

self:oBD:SetPageBoundaries()

self:ResetRowColDrops()

//=======================================================================

self:oBD:PrintPage += PrintPageEventHandler{self, @MyTestDocument()}

if !self:Preview

//oBD:Print()

else

self:printPreviewControl1:Document := self:oBD

self:printPreviewControl1:UseAntiAlias := true

self:printPreviewControl1:Document:DocumentName := "First Test :"

self:oPreviewDlg := PrintPreviewDialog{}

self:oPreviewDlg:Document := self:oBD

self:oPreviewDlg:ShowDialog()

//self:oPreviewDlg:Show() //--- blank shows ! ---

endif

self:oBD:PrintPage -= PrintPageEventHandler{self, @MyTestDocument()}

//=======================================================================

self:oBD:Dispose()

self:oBD := null_object

return

PUBLIC METHOD ShowDocumentE() AS VOID

//MessageBox.Show("printer name ...",self:cPrinterName+":")

//self:oBD := BestDocument{"TestShow","OKI C5600 linksys"}

self:oBD := BestDocument{"TestShow",self:cPrinterName}

//self:oBD := BestDocument{"TestShow",""}

self:oBD:ResetOrientation("P")

//self:oBD:ResetOrientation("L")

self:oBD:SetPageBoundaries()

self:ResetRowColDrops()

//=======================================================================

self:oBD:PrintPage += PrintPageEventHandler{self, @MyTestDocumentE()}

if !self:Preview

//oBD:Print()

else

self:printPreviewControl1:Document := self:oBD

self:printPreviewControl1:UseAntiAlias := true

self:printPreviewControl1:Document:DocumentName := "First Test :"

self:oPreviewDlg := PrintPreviewDialog{}

self:oPreviewDlg:Document := self:oBD

self:oPreviewDlg:ShowDialog()

//self:oPreviewDlg:Show() //--- blank shows ! ---

endif

self:oBD:PrintPage -= PrintPageEventHandler{self, @MyTestDocumentE()}

//=======================================================================

self:oBD:Dispose()

self:oBD := null_object

return

PUBLIC METHOD TestPrint_Click(sender AS OBJECT, e AS System.EventArgs) AS VOID

//MessageBox.Show("In method handler !","My testing ...")

ShowDocumentA()

return

PUBLIC METHOD InvoiceQ_Click(sender AS OBJECT, e AS System.EventArgs) AS VOID

ShowDocumentQ()

return

PUBLIC METHOD DocumentC_Click(sender AS OBJECT, e AS System.EventArgs) AS VOID

ShowDocumentC()

return

PUBLIC METHOD pbMailMerge_Click(sender AS OBJECT, e AS System.EventArgs) AS VOID

try

DoDisplayDocMM()

catch oEX as SystemException

MessageBox.Show(oEX:Message,"my Error display MM :")

end try

return

PUBLIC METHOD DoDisplayDocMM() AS VOID

//----- set up MM DataTable object -----

SELF:MakeMailMergeDataTable()

SELF:SetUpDataTable(SELF:oMMDT)

SELF:Landscape := FALSE

SELF:cWhich := "MM"

self:FillRowColDrops()

//=======================================================================

//self:oBD := BestDocument{"Mail Merge","OKI C5600 linksys"}

self:oBD := BestDocument{"TestShow",""}

self:oBD:ResetOrientation("P")

//self:oBD:ResetOrientation("L")

self:oBD:SetPageBoundaries()

self:oBD:PrintPage += PrintPageEventHandler{self, @MyTestDocumentMM()}

self:oBD:PrintPage += PrintPageEventHandler{self, @MyTestDocumentMMpart2()}

//=========================================================================

try

if !self:Preview

//oBD:Print()

else

self:printPreviewControl1:Document := self:oBD

self:printPreviewControl1:UseAntiAlias := true

self:printPreviewControl1:Document:DocumentName := "Fifth Test :"

self:oPreviewDlg := PrintPreviewDialog{}

self:oPreviewDlg:Document := self:oBD

self:oPreviewDlg:ShowDialog()

endif

catch oEX as SystemException

MessageBox.Show(oEX:Message,"my Error display MM :")

end try

self:oBD:PrintPage -= PrintPageEventHandler{self, @MyTestDocumentMM()}

self:oBD:PrintPage -= PrintPageEventHandler{self, @MyTestDocumentMMpart2()}

//=========================================================================

return

PUBLIC METHOD SetUpDataTable(oDTin AS System.Data.DataTable) AS VOID

oMMDT := oDTin

oDCC := SELF:oMMDT:Columns

SELF:NumberOfColumns := oDCC:Count

oDRC := SELF:oMMDT:Rows

SELF:NumberOfRows := oDRC:Count

SELF:nRowPointer := 1

RETURN

PUBLIC METHOD MakeMailMergeDataTable() AS VOID

SELF:cFocusXML := Application.StartupPath+"\FMCmm2.XML"

self:cFocusXSD := Application.StartupPath+"\FMCmm2.XSD"

//--- below needs adjusting ! ---

//SELF:cFocusXML := "Data_XML_MM\FMCmm2.XML"

//self:cFocusXSD := "Data_XML_MM\FMCmm2.XSD"

SELF:AutoLoadXMLpair()

RETURN

PUBLIC METHOD AutoLoadXMLpair() AS VOID

LOCAL cFileD, cFileS, cCaption AS STRING

LOCAL cTableName as string

LOCAL oDCC AS DataColumnCollection

LOCAL oDRC AS DataRowCollection

cFileD := SELF:cFocusXML

cFileS := SELF:cFocusXSD

IF File.Exists(cFileD)

//MessageBox.Show("Data as XML exists!")

ELSE

MessageBox.Show("File NOT found :- "+cFileD)

ENDIF

IF File.Exists(cFileS)

//MessageBox.Show("Schema for XML exists!")

ELSE

MessageBox.Show("File NOT found :- "+cFileS)

ENDIF

SELF:oMMDT := DataTable{}

SELF:oMMDT:ReadXmlSchema(cFileS)

cTableName := SELF:oMMDT:TableName

//MessageBox.Show(cTableName:ToString())

cCaption := SELF:Text

SELF:Text := cCaption+" Reporting on : "+cTableName:ToString()

SELF:oMMDT:ReadXml(cFileD)

oDCC := SELF:oMMDT:Columns

NumberOfColumns := oDCC:Count

//MessageBox.Show("Number of Columns : "+NumberOfColumns:ToString())

oDRC := SELF:oMMDT:Rows

NumberOfRows := oDRC:Count

//MessageBox.Show("Number of Rows : "+NumberOfRows:ToString())

return

PUBLIC METHOD MyTestDocumentMM(o AS OBJECT, e AS System.Drawing.Printing.PrintPageEventArgs) AS VOID

IF SELF:oMMDT = NULL_OBJECT

//----- warning -----

MessageBox.Show("Data Table is not instantiated !")

e:HasMorePages := FALSE

RETURN

ENDIF

//==========================================================

LOCAL oFont10A, oFont12A AS Font

LOCAL oFont12Ab, oFont8A, oFont16A, oFont22Ab AS Font

oBD:SetGraphics(e:Graphics)

oFont22Ab := Font{"Arial",22,FontStyle.Bold}

oFont16A := Font{"Arial",16}

oFont12A := Font{"Arial",12}

oFont12Ab := Font{"Arial",12,FontStyle.Bold}

oFont10A := Font{"Arial",10}

oFont8A := Font{"Arial",8}

//---------------------------------------------------

//============ place your MM test code here ============

oBD:ResetLinesPerPage(36)

SELF:GetMakeMMobject(SELF:nRowPointer) //----- make relevant oMM object -----

oBD:Font := oFont22Ab

LOCAL oPGC AS PageCoords

oPGC := PageCoords{5,10}

oBD:Text(oPGC,oMM:Title)

oPGC := PageCoords{6,10}

oBD:Text(oPGC,oMM:Firstname)

oPGC := PageCoords{7,10}

oBD:Text(oPGC,oMM:Lastname)

oPGC := PageCoords{8,10}

oBD:Text(oPGC,oMM:Add1)

oPGC := PageCoords{9,10}

oBD:Text(oPGC,oMM:Add2)

oPGC := PageCoords{10,10}

oBD:Text(oPGC,oMM:Add3)

oPGC := PageCoords{11,10}

oBD:Text(oPGC,oMM:PCode)

//=============== end of test code ==================

SELF:nRowPointer +=1

IF SELF:nRowPointer > SELF:NumberOfRows

e:HasMorePages := FALSE

SELF:nRowPointer := 1

ELSE

e:HasMorePages := TRUE

ENDIF

RETURN

PUBLIC METHOD MyTestDocumentMMpart2(o AS OBJECT, e AS System.Drawing.Printing.PrintPageEventArgs) AS VOID

IF SELF:oMMDT = NULL_OBJECT

//----- warning -----

MessageBox.Show("Data Table is not instantiated !")

e:HasMorePages := FALSE

RETURN

ENDIF

//==========================================================

LOCAL oFont10A, oFont12A AS Font

LOCAL oFont12Ab, oFont8A, oFont16A, oFont22Ab AS Font

//oBD:SetGraphics(e:Graphics)

oFont22Ab := Font{"Arial",22,FontStyle.Bold}

oFont16A := Font{"Arial",16}

oFont12A := Font{"Arial",12}

oFont12Ab := Font{"Arial",12,FontStyle.Bold}

oFont10A := Font{"Arial",10}

oFont8A := Font{"Arial",8}

//---------------------------------------------------

//============ place your MM test code here ============

oBD:Font := oFont22Ab

LOCAL oPGC AS PageCoords

oPGC := PageCoords{25,10}

oBD:Text(oPGC,oMM:Telephone)

oPGC := PageCoords{26,10}

oBD:Text(oPGC,oMM:Email)

oPGC := PageCoords{27,10}

oBD:Text(oPGC,oMM:LastOrder:ToLongDateString())

oPGC := PageCoords{28,10}

oBD:Text(oPGC,oMM:DoB:ToLongDateString())

oPGC := PageCoords{29,10}

oBD:Text(oPGC,oMM:Owing:ToString())

oPGC := PageCoords{30,10}

oBD:Text(oPGC,oMM:Add3)

oPGC := PageCoords{31,10}

oBD:Text(oPGC,oMM:XmasList:ToString())

//=============== end of test code ==================

return

PUBLIC METHOD GetMakeMMobject(nRow AS INT) AS VOID

LOCAL oDR AS DataRow

oDR := SELF:oDRC:Item[nRow-1]

oMM := MailMerge{}

oMM:Title := (STRING)oDR:Item[0]

oMM:Firstname := (STRING)oDR:Item[1]

oMM:Lastname := (STRING)oDR:Item[2]

oMM:DoB := (DateTime)oDR:Item[3]

oMM:LastOrder := (DateTime)oDR:Item[4]

oMM:XmasList := (Boolean)oDR:Item[5]

oMM:Owing := (Double)oDR:Item[6]

oMM:Add1 := (STRING)oDR:Item[7]

oMM:Add2 := (STRING)oDR:Item[8]

oMM:Add3 := (STRING)oDR:Item[9]

oMM:PCode := (STRING)oDR:Item[10]

oMM:Telephone := (STRING)oDR:Item[11]

oMM:Email := (STRING)oDR:Item[12]

return

PUBLIC METHOD ImageDoc_Click(sender AS OBJECT, e AS System.EventArgs) AS VOID

ShowDocument()

return

PUBLIC METHOD MultiGridE_Click(sender AS OBJECT, e AS System.EventArgs) AS VOID

ShowDocumentE()

return

PUBLIC METHOD cbPrinters_SelectedIndexChanged(sender AS OBJECT, e AS System.EventArgs) AS VOID

SELF:cPrinterName := SELF:cbPrinters:SelectedItem:ToString()

self:Text := "Test Bed Printing : BestDocument class : "+"[ printer >> "+SELF:cPrinterName+" ]"

return

PUBLIC METHOD cbRows_SelectionChangeCommitted(sender AS OBJECT, e AS System.EventArgs) AS VOID

LOCAL nRow AS INT

nRow := (INT)SELF:cbRows:SelectedItem

self:printPreviewControl1:Rows := nRow

return

PUBLIC METHOD cbColumns_SelectionChangeCommitted(sender AS OBJECT, e AS System.EventArgs) AS VOID

LOCAL nColumn AS INT

nColumn := (INT)SELF:cbColumns:SelectedItem

self:printPreviewControl1:Columns := nColumn

return

PRIVATE METHOD cbStartPage_SelectionChangeCommitted(sender AS OBJECT, e AS System.EventArgs) AS VOID

LOCAL nStart AS INT

nStart := (INT)SELF:cbStartPage:SelectedItem

self:printPreviewControl1:StartPage := nStart-1

return

PRIVATE METHOD cbZoom_SelectionChangeCommitted(sender AS OBJECT, e AS System.EventArgs) AS VOID

LOCAL nZoom AS Double

nZoom := (Double)SELF:cbZoom:SelectedItem

self:printPreviewControl1:Zoom := nZoom

SELF:cbAutoZoom:Checked := FALSE

return

PRIVATE METHOD cbAutoZoom_CheckStateChanged(sender AS OBJECT, e AS System.EventArgs) AS VOID

if self:cbAutoZoom:Checked

self:printPreviewControl1:AutoZoom := SELF:cbAutoZoom:Checked

SELF:cbZoom:SelectedIndex := -1

else

SELF:cbZoom:SelectedIndex := 4

endif

return

PRIVATE METHOD cbResetDrops_CheckStateChanged(sender AS OBJECT, e AS System.EventArgs) AS VOID

self:ResetRowColDrops()

cbResetDrops:Checked := false

return

END CLASS

//==================================================================

PUBLIC CLASS MailMerge

PUBLIC Title AS STRING

PUBLIC Firstname AS STRING

PUBLIC Lastname AS STRING

PUBLIC Add1 AS STRING

PUBLIC Add2 AS STRING

PUBLIC Add3 AS STRING

PUBLIC PCode AS STRING

PUBLIC Telephone AS STRING

PUBLIC Email AS STRING

PUBLIC DoB AS System.DateTime

PUBLIC LastOrder AS System.DateTime

PUBLIC XmasList AS LOGIC

PUBLIC Owing AS REAL8

PUBLIC CONSTRUCTOR()

SUPER()

SELF:Initialize()

RETURN

PUBLIC VIRTUAL METHOD Initialize() AS VOID

Title := ""

Firstname := ""

Lastname := ""

Add1 := ""

Add2 := ""

Add3 := ""

PCode := ""

Telephone := ""

Email := ""

DoB := DateTime.MinValue

LastOrder := DateTime.MinValue

XmasList := FALSE

Owing := 0.0

RETURN

END CLASS

END NAMESPACE

========[ Program.prg ]=============================================================================

USING System

USING System.Collections.Generic

USING System.Linq

USING System.Text

USING System.Windows.Forms

USING XS_TestBedPrinting

BEGIN NAMESPACE XS_TestBedPrinting

[STAThread] ;

FUNCTION Start() AS VOID

   Application.EnableVisualStyles()

   Application.SetCompatibleTextRenderingDefault( FALSE )

   Application.Run( Form1{} )

  RETURN

END NAMESPACE

=====================================================================================

=====================================================================================

===========[ class library BestDocument etc. ]==========================================================================

USING System

USING System.Collections.Generic

USING System.Linq

USING System.Text

//

// Class1.prg

//

//----- copied from VIDE development project -----

//-----

// Application : BestDocument

// BestDocument.prg , Started-Created : 16/08/2008 08:59:12 AM

USING System.Windows.Forms

USING System.Drawing

USING System.Drawing.Printing

USING System.IO

USING System.Drawing.Drawing2D

USING System.Collections

USING System.Data

USING Microsoft.VisualBasic

Using System.Xml.Serialization

BEGIN NAMESPACE ClassLibraryBESTdocument

CLASS BestDocument INHERIT PrintDocument

EXPORT JobName AS STRING

EXPORT Font AS Font

EXPORT Brush AS Brush

EXPORT Pen AS Pen

EXPORT Color AS Color

EXPORT Size AS Double

EXPORT NextLine AS INT

EXPORT Landscape AS Boolean

EXPORT LinesPerPage, PageNumber, NumberOfPages, FirstPage, LastPage AS INT

EXPORT MyPrinterName AS STRING

EXPORT AutoPage AS Boolean

EXPORT g, Graphics AS Graphics

EXPORT PrinterName AS STRING

PROTECT HardMarginX, HardMarginY AS Double

PROTECT CanvasLength, CanvasWidth AS Double

PROTECT PCCL, PCCW AS Double //------- percentage canvas length/.......

PROTECT PCPL, PCPW AS Double //------- percentage page length/........

PROTECT RM, LM, TM, BM AS Double //-------- margins Right/Left/......

PROTECT PCLM, PCRM, PCTM, PCBM AS Double //-------- % margins Right/Left/......

PROTECT BLP, BRP, TLP, TRP AS Point //---- bottom left/ ......

PROTECT PageLength, PageWidth AS Double

PROTECT LineHeightP AS Double //-------- vertical distance between Page lines (Portrait)---------

PROTECT LineHeightL AS Double //-------- vertical distance between Page lines (Landscape) ---------

PROTECT LineHeight AS Double //-------- one used for actual printing ---------

EXPORT Error AS Boolean

EXPORT LastErrorMessage AS STRING

EXPORT DecimalAligns AS Double[]

EXPORT oPageSettings AS PageSettings

EXPORT cDefaultPrinter AS STRING

EXPORT nPenWidth AS Double

CONSTRUCTOR(cJobName AS STRING, cPrinterName AS STRING)

SUPER()

cPrinterName := cPrinterName:Trim()

IF cPrinterName == ""

LOCAL oPS AS PrinterSettings

oPS := PrinterSettings{}

SELF:cDefaultPrinter := oPS:PrinterName

cPrinterName := SELF:cDefaultPrinter

ENDIF

SELF:MyPrinterName := cPrinterName

SELF:PrinterName := cPrinterName

SELF:JobName := cJobName:Trim()

SELF:PrinterSettings:PrinterName :=cPrinterName

SELF:Initialize()

RETURN

METHOD Initialize() AS VOID

SELF:Landscape := FALSE

SELF:LinesPerPage := 60 //--- default setting ---

SELF:NextLine := 1 //--- could this be zero ? ---

//----- set default font, brush and pen -----

SELF:Font := Font{"Arial",12}

SELF:Brush := SolidBrush{Color.Black}

SELF:Pen := Pen{SELF:Brush, 1.0}

SELF:Color := Color.Black

DecimalAligns := Double[]{1}

RETURN

METHOD SetGraphics(g AS Graphics) AS VOID

SELF:Graphics := g

SELF:g := g

RETURN

METHOD SetPageBoundaries() AS VOID

LOCAL oPS AS PrinterSettings

LOCAL oMyArea AS RectangleF

LOCAL nHardX, nHardY AS Double

LOCAL nMLeft, nMRight, nMTop, nMBottom AS Double

LOCAL nPCMLeft, nPCMRight, nPCMTop, nPCMBottom AS Double

LOCAL nPAreaLeft, nPAreaRight, nPAreaTop, nPAreaBottom AS Double

oPS := PrinterSettings{}

oPS:PrinterName := SELF:PrinterName

//------------ need to select the printer first ---------------

IF SELF:Landscape

oPS:DefaultPageSettings:Landscape := TRUE

ELSE

oPS:DefaultPageSettings:Landscape := FALSE

ENDIF

oPageSettings := oPS:DefaultPageSettings

oMyArea := oPageSettings:PrintableArea

nHardX := oPageSettings:HardMarginX

nHardY := oPageSettings:HardMarginY

SELF:HardMarginX := nHardX

SELF:HardMarginY := nHardY

nPAreaLeft := oMyArea:Left

nPAreaRight := oMyArea:Right

nPAreaTop := oMyArea:Top

nPAreaBottom := oMyArea:Bottom

SELF:CanvasLength := nPAreaBottom - nPAreaTop

SELF:CanvasWidth := nPAreaRight - nPAreaLeft

SELF:PCCL := SELF:CanvasLength*0.01

SELF:PCCW := SELF:CanvasWidth*0.01

nPCMLeft := 2*SELF:PCCW

nPCMRight := 2*SELF:PCCW

nPCMTop := 2*SELF:PCCL

nPCMBottom := 2*SELF:PCCL

nMLeft := nPAreaLeft

nMRight := nPAreaRight

nMTop := nPAreaTop

nMBottom := nPAreaBottom

SELF:LM := nMLeft

SELF:RM := nMRight

SELF:TM := nMTop

SELF:BM := nMBottom

SELF:PCLM := nPCMLeft

SELF:PCRM := nPCMRight

SELF:PCTM := nPCMTop

SELF:PCBM := nPCMBottom

SELF:BLP := Point{(INT)Math.Floor(SELF:LM), (INT)Math.Floor(SELF:BM)}

SELF:BRP := Point{(INT)Math.Floor(SELF:CanvasWidth-SELF:LM), (INT)Math.Floor(SELF:BM)}

SELF:TLP := Point{(INT)Math.Floor(SELF:LM), (INT)Math.Floor(SELF:CanvasLength-SELF:BM)}

SELF:TRP := Point{(INT)Math.Floor(SELF:CanvasWidth-SELF:LM), (INT)Math.Floor(SELF:CanvasLength-SELF:BM)}

SELF:PageLength := SELF:CanvasLength

SELF:PageWidth := SELF:CanvasWidth

SELF:LineHeightP := SELF:PageLength/(SELF:LinesPerPage+2)//---- adjusted 'al dente' to fit the screen view ----

SELF:LineHeightL := SELF:PageWidth/(SELF:LinesPerPage+2)//---- adjusted 'al dente' to fit the screen view ----

IF oPS:DefaultPageSettings:Landscape

SELF:LineHeight := SELF:LineHeightL

SELF:PageLength := SELF:CanvasWidth

SELF:PageWidth := SELF:CanvasLength

ELSE

SELF:LineHeight := SELF:LineHeightP

ENDIF

SELF:PCPL := SELF:PageLength*0.01

SELF:PCPW := SELF:PageWidth*0.01

RETURN

METHOD ResetLinesPerPage(nLPP AS INT) AS VOID

IF nLPP < 20

nLPP := 20

ENDIF

IF nLPP > 130

nLPP := 130

ENDIF

SELF:LinesPerPage := nLPP

SELF:LineHeight := SELF:PageLength/(SELF:LinesPerPage+2)//---- adjusted 'al dente' to fit the screen view ----

RETURN

METHOD ResetOrientation(cOrient AS STRING) AS VOID

IF cOrient:ToUpper():Trim() == "L"

SELF:Landscape := TRUE

SELF:DefaultPageSettings:Landscape := TRUE

ELSE

SELF:Landscape := FALSE

SELF:DefaultPageSettings:Landscape := FALSE

ENDIF

RETURN

METHOD ReSetBrushAndPen() AS VOID

SELF:ApplyPenWdith(1.0)

RETURN

METHOD ApplyPenWdith(nWidth AS Double) AS VOID

SELF:nPenWidth := nWidth

SELF:Brush := SolidBrush{SELF:Color}

SELF:Pen := Pen{SELF:Brush, (Single)SELF:nPenWidth}

RETURN

METHOD ApplyNewColor(oColor AS Color) AS VOID

SELF:Color := oColor

SELF:Brush := SolidBrush{SELF:Color}

SELF:Pen := Pen{SELF:Brush, (Single)SELF:nPenWidth}

RETURN

METHOD SetNextLine(nNext AS INT) AS VOID

IF nNext < 1

nNext := 1

ENDIF

IF nNext > SELF:NextLine

nNext := SELF:NextLine

ENDIF

SELF:NextLine := nNext

RETURN

METHOD GetMargins() AS Double[]

LOCAL aMargins AS Double[]

aMargins := Double[]{4}

aMargins[1] := SELF:LM

aMargins[2] := SELF:RM

aMargins[3] := SELF:TM

aMargins[4] := SELF:BM

RETURN aMargins

METHOD PrintableAreaDetails() AS STRING

LOCAL cDetails AS STRING

LOCAL cR2 AS STRING

cR2 := CRLF+CRLF

cDetails := "Details ..."+cR2

cDetails += "Printer : "+SELF:PrinterName:ToString()+cR2

cDetails += "Lines per Page : "+SELF:LinesPerPage:ToString()+cR2

cDetails += "Hard Margin X : "+SELF:HardMarginX:ToString()+CRLF

cDetails += "Hard Margin Y : "+SELF:HardMarginY:ToString()+cR2

cDetails += "Canvas Length : "+SELF:CanvasLength:ToString()+CRLF

cDetails += "Canvas Width : "+SELF:CanvasWidth:ToString()+cR2

cDetails += "Left Margin : "+SELF:LM:ToString()+CRLF

cDetails += "Right Margin : "+SELF:RM:ToString()+CRLF

cDetails += "Top Margin : "+SELF:TM:ToString()+CRLF

cDetails += "Bottom Margin : "+SELF:BM:ToString()+cR2

cDetails += "Line Height : "+SELF:LineHeight:ToString()+cR2

RETURN cDetails

METHOD TextLength(cText AS STRING) AS Double

LOCAL oSizeF AS SizeF

LOCAL fResult AS Double

LOCAL nLen AS INT

nLen := cText:Length

fResult := 0.0

IF nLen < 1

RETURN fResult

ENDIF

LOCAL oDrawFormat AS StringFormat

LOCAL oPoint AS Point

oPoint := Point{100,100} //----- less risky than 0,0 !? -----

oDrawFormat := StringFormat{}

oDrawFormat:FormatFlags := StringFormatFlags.MeasureTrailingSpaces

oSizeF := SELF:g:MeasureString(cText, SELF:Font, oPoint, oDrawFormat)

fResult := oSizeF:Width

RETURN fResult

METHOD TextHeight(cText AS STRING) AS Double

LOCAL oSizeF AS SizeF

LOCAL fResult, fHeight AS Double

oSizeF := SELF:g:MeasureString(cText, SELF:Font)

fHeight := oSizeF:Height

fResult := 100*fHeight/SELF:PageLength

RETURN fResult

METHOD ConvertGraphCoords2Point(oGC AS GraphCoords) AS Point

LOCAL oPoint AS Point

oPoint := ConvertPaperCoords2Point(ConvertGraphCoords2PaperCoords(oGC))

RETURN oPoint

METHOD ConvertPageCoords2Point(oPGC AS PageCoords) AS Point

LOCAL nLine, nAcross AS Double

LOCAL nLPP AS INT

LOCAL oPoint AS Point

LOCAL nY, nX AS Double

LOCAL iY, iX AS INT

LOCAL r8Line, r8LineHeight, r8Across, r8LM, r8PCPW AS Double

LOCAL r8TM AS Double

nLPP := SELF:LinesPerPage

r8LineHeight := SELF:LineHeight

nLine := oPGC:nLine

nAcross := oPGC:nAcross

IF nLine < 0 .or. nLine > nLPP

//--------- warn box ---------

RETURN Point{0,0}

ENDIF

IF nAcross < 0 .or. nAcross > 100

//---------- warn box ---------

RETURN Point{0,0}

ENDIF

r8TM := SELF:TM

r8Line := nLine

nY := r8TM+(r8Line)*r8LineHeight

r8LM := SELF:LM

r8Across := nAcross

r8PCPW := SELF:PCPW

nX := r8LM+r8Across*r8PCPW

iY := (INT)Math.Floor(nY+0.5)

iX := (INT)Math.Floor(nX+0.5)

oPoint := Point{iX, iY}

RETURN oPoint

METHOD ConvertPaperCoords2Point(oPPC AS PaperCoords) AS Point

LOCAL nDown, nAcross AS Double

LOCAL oPoint AS Point

LOCAL nY, nX AS Double

LOCAL iY, iX AS INT

LOCAL r8LM, r8PCPW AS Double

LOCAL r8TM, r8PCPL AS Double

nDown := oPPC:nDown

nAcross := oPPC:nAcross

IF nDown < 0 .or. nDown > 100

//--------- warn box ---------

RETURN Point{0,0}

ENDIF

IF nAcross < 0 .or. nAcross > 100

//---------- warn box ---------

RETURN Point{0,0}

ENDIF

r8TM := SELF:TM

r8PCPL := SELF:PCPL

nY := r8TM+nDown*r8PCPL

r8LM := SELF:LM

r8PCPW := SELF:PCPW

nX := r8LM+nAcross*r8PCPW

iY := (INT)Math.Floor(nY+0.5)

iX := (INT)Math.Floor(nX+0.5)

oPoint := Point{iX, iY}

RETURN oPoint

METHOD ConvertPoint2PaperCoords(oPoint AS Point) AS PaperCoords

LOCAL oPPC AS PaperCoords

LOCAL nDown, nAcross AS Double

LOCAL r8TM, r8PCPL AS Double

LOCAL r8LM, r8PCPW AS Double

LOCAL nY, nX AS Double

nX := (Double)oPoint:X

nY := (Double)oPoint:Y

r8TM := SELF:TM

r8PCPL := SELF:PCPL

nDown := (nY-r8TM)/r8PCPL

r8LM := SELF:LM

r8PCPW := SELF:PCPW

nAcross := (nX-r8LM)/r8PCPW

oPPC := PaperCoords{nDown, nAcross}

RETURN oPPC

METHOD ConvertPoint2PageCoords(oPoint AS Point) AS PageCoords

LOCAL oPGC AS PageCoords

LOCAL nY, nX AS Double

LOCAL r8LM, r8PCPW, nAcross, r8TM, nLine AS Double

nX := (Double)oPoint:X

nY := (Double)oPoint:Y

r8LM := SELF:LM

r8PCPW := SELF:PCPW

nAcross := (nX-r8LM)/r8PCPW

r8TM := SELF:TM

nLine := (nY-r8TM)/SELF:LineHeight

oPGC := PageCoords{nLine, nAcross}

RETURN oPGC

METHOD ConvertPoint2GraphCoords(oPoint AS Point) AS GraphCoords

LOCAL oGC AS GraphCoords

LOCAL oPPC AS PaperCoords

oPPC := ConvertPoint2PaperCoords(oPoint)

oGC := ConvertPaperCoords2GraphCoords(oPPC)

RETURN oGC

METHOD ConvertGraphCoords2PaperCoords(oGC AS GraphCoords) AS PaperCoords

LOCAL oPPC AS PaperCoords

oPPC := PaperCoords{100-oGC:nUp, oGC:nAcross}

RETURN oPPC

METHOD ConvertPaperCoords2GraphCoords(oPPC AS PaperCoords) AS GraphCoords

LOCAL oGC AS GraphCoords

oGC := GraphCoords{100-oPPC:nDown, oPPC:nAcross}

RETURN oGC

METHOD ConvertPageCoords2PaperCoords(oPGC AS PageCoords) AS PaperCoords

LOCAL oPPC AS PaperCoords

LOCAL oPoint AS Point

oPoint := ConvertPageCoords2Point(oPGC)

oPPC := ConvertPoint2PaperCoords(oPoint)

RETURN oPPC

METHOD ConvertPaperCoords2PageCoords(oPPC AS PaperCoords) AS PageCoords

LOCAL oPGC AS PageCoords

LOCAL oPoint AS Point

oPoint := ConvertPaperCoords2Point(oPPC)

oPGC := ConvertPoint2PageCoords(oPoint)

RETURN oPGC

METHOD Text(oPGC AS PageCoords, cText AS STRING) AS VOID

LOCAL oPoint AS Point

LOCAL nLine AS double

nLine := oPGC:nLine

oPoint := ConvertPageCoords2Point(oPGC)

SELF:g:DrawString(cText , SELF:Font , SELF:Brush , oPoint)

SELF:NextLine := (INT)Math.Round(nLine + 1.5)

RETURN

METHOD TextRestrictRHS(nAcross AS Double, cText AS STRING, nAcrossRHS AS Double) AS STRING

LOCAL cResult AS STRING

cResult := cText

IF nAcrossRHS < nAcross+5

nAcrossRHS := nAcross+5

ENDIF

LOCAL nPCtarget, nPCmax, nPCnow AS Double

nPCmax := 100-nAcross

nPCtarget := nAcrossRHS - nAcross

IF nPCtarget > nPCmax

nPCtarget := nPCmax

ENDIF

nPCnow := PCTextLength(cText)

IF nPCnow <= nPCtarget

RETURN cResult

ENDIF

LOCAL nLen AS INT

DO WHILE nPCnow > nPCtarget

nLen := cText:Length

cText := cText:Substring(0,nLen-1)

nPCnow := PCTextLength(cText)

ENDDO

cResult := cText

RETURN cResult

METHOD TextRestrictRHS(cText AS STRING, nWide AS Double) AS STRING

LOCAL cResult AS STRING

cResult := cText

IF nWide < 5

nWide := 5

ENDIF

IF nWide > 100

nWide := 100

ENDIF

LOCAL nPCtarget, nPCnow AS Double

nPCtarget := nWide

nPCnow := PCTextLength(cText)

IF nPCnow <= nPCtarget

RETURN cResult

ENDIF

LOCAL nLen AS INT

DO WHILE nPCnow > nPCtarget

nLen := cText:Length

cText := cText:Substring(0,nLen-1)

nPCnow := PCTextLength(cText)

ENDDO

cResult := cText

RETURN cResult

METHOD Text(oPPC AS PaperCoords, cText AS STRING) AS VOID

LOCAL oPoint AS Point

oPoint := ConvertPaperCoords2Point(oPPC)

SELF:g:DrawString(cText , SELF:Font , SELF:Brush , oPoint)

RETURN

METHOD Text(oGC AS GraphCoords, cText AS STRING) AS VOID

LOCAL oPoint AS Point

LOCAL oPPC AS PaperCoords

oPPC := ConvertGraphCoords2PaperCoords(oGC)

oPoint := ConvertPaperCoords2Point(oPPC)

SELF:g:DrawString(cText , SELF:Font , SELF:Brush , oPoint)

RETURN

METHOD TextChop(oPPC AS PaperCoords, cText AS STRING, nWide AS Double) AS VOID

LOCAL cResult AS STRING

cResult := SELF:TextRestrictRHS(cText, nWide)

SELF:Text(oPPC, cResult)

RETURN

METHOD TextRight(oPGC AS PageCoords, cText AS STRING) AS VOID

LOCAL oPoint AS Point

LOCAL nLine, nTextLen, nNewAcross, nAcross AS double

LOCAL oPGCnew AS PageCoords

nLine := oPGC:nLine

nAcross := oPGC:nAcross

nTextLen := SELF:PCTextLength(cText)

nNewAcross := nAcross-nTextLen

IF nNewAcross < 0

nNewAcross := 0

ENDIF

oPGCnew := PageCoords{nLine, nNewAcross}

oPoint := ConvertPageCoords2Point(oPGCnew)

SELF:g:DrawString(cText , SELF:Font , SELF:Brush , oPoint)

SELF:NextLine := (INT)Math.Round(nLine + 1.5)

RETURN

METHOD TextRight(oPPC AS PaperCoords, cText AS STRING) AS VOID

LOCAL oPoint AS Point

LOCAL nDown, nTextLen, nNewAcross, nAcross AS double

LOCAL oPPCnew AS PaperCoords

nDown := oPPC:nDown

nAcross := oPPC:nAcross

nTextLen := SELF:PCTextLength(cText)

nNewAcross := nAcross-nTextLen

IF nNewAcross < 0

nNewAcross := 0

ENDIF

oPPCnew := PaperCoords{nDown, nNewAcross}

oPoint := ConvertPaperCoords2Point(oPPCnew)

SELF:g:DrawString(cText , SELF:Font , SELF:Brush , oPoint)

RETURN

METHOD TextRight(oGC AS GraphCoords, cText AS STRING) AS VOID

LOCAL oPoint AS Point

LOCAL nDown, nTextLen, nNewAcross, nAcross AS Double

LOCAL oPPCnew, oPPC AS PaperCoords

oPPC := SELF:ConvertGraphCoords2PaperCoords(oGC)

nDown := oPPC:nDown

nAcross := oPPC:nAcross

nTextLen := SELF:PCTextLength(cText)

nNewAcross := nAcross-nTextLen

IF nNewAcross < 0

nNewAcross := 0

ENDIF

oPPCnew := PaperCoords{nDown, nNewAcross}

oPoint := ConvertPaperCoords2Point(oPPCnew)

SELF:g:DrawString(cText , SELF:Font , SELF:Brush , oPoint)

RETURN

METHOD TextUp(oPGC AS PageCoords, cText AS STRING) AS VOID

LOCAL oPoint AS Point

oPoint := ConvertPageCoords2Point(oPGC)

LOCAL oDrawFormat AS StringFormat

oDrawFormat := StringFormat{}

oDrawFormat:FormatFlags := StringFormatFlags.DirectionVertical

SELF:g:DrawString(cText , SELF:Font , SELF:Brush , oPoint, oDrawFormat )

RETURN

METHOD TextUp(oPPC AS PaperCoords, cText AS STRING) AS VOID

LOCAL oPoint AS Point

oPoint := ConvertPaperCoords2Point(oPPC)

LOCAL oDrawFormat AS StringFormat

oDrawFormat := StringFormat{}

oDrawFormat:FormatFlags := StringFormatFlags.DirectionVertical

SELF:g:DrawString(cText , SELF:Font , SELF:Brush , oPoint, oDrawFormat )

RETURN

METHOD TextUpTo(oPPC AS PaperCoords, cText AS STRING) AS VOID

LOCAL oPoint AS Point

LOCAL oSizeF AS SizeF

LOCAL nX, nY AS INT

oPoint := ConvertPaperCoords2Point(oPPC)

nX := oPoint:X

nY := oPoint:Y

LOCAL oDrawFormat AS StringFormat

oDrawFormat := StringFormat{}

oDrawFormat:FormatFlags := StringFormatFlags.DirectionVertical

oSizeF := SELF:g:MeasureString(cText, SELF:Font, oPoint, oDrawFormat)

nY -= (INT)oSizeF:Height

oPoint := Point{nX, nY}

SELF:g:DrawString(cText , SELF:Font , SELF:Brush , oPoint, oDrawFormat )

RETURN

METHOD TextUp(oGC AS GraphCoords, cText AS STRING) AS VOID

LOCAL oPoint AS Point

oPoint := ConvertGraphCoords2Point(oGC)

LOCAL oDrawFormat AS StringFormat

oDrawFormat := StringFormat{}

oDrawFormat:FormatFlags := StringFormatFlags.DirectionVertical

SELF:g:DrawString(cText , SELF:Font , SELF:Brush , oPoint, oDrawFormat )

RETURN

METHOD Block(oPGC AS PageCoords, aText AS STRING[]) AS VOID

LOCAL i, nLen AS INT

LOCAL cLine AS STRING

LOCAL nLine, nAcross AS Double

LOCAL oPGCthis AS PageCoords

oPGCthis := oPGC

nLine := oPGCthis:nLine

nAcross := oPGCthis:nAcross

nLen := aText:Length

FOR i := 1 UPTO nLen

cLine := aText[i]

SELF:Text(oPGCthis, cLine)

nLine +=1

oPGCthis := PageCoords{nLine,nAcross}

NEXT

SELF:NextLine := (INT)Math.Round(nLine + 0.5)

RETURN

METHOD Block(oPPC AS PaperCoords, aText AS STRING[]) AS VOID

LOCAL i, nLen AS INT

LOCAL cLine AS STRING

LOCAL nDown, nAcross, nAdTextHeight AS Double

LOCAL oPPCthis AS PaperCoords

oPPCthis := oPPC

nDown := oPPCthis:nDown

nAcross := oPPCthis:nAcross

nLen := aText:Length

nAdTextHeight := SELF:TextHeight("PLQjy")

FOR i := 1 UPTO nLen

cLine := aText[i]

SELF:Text(oPPCthis, cLine)

//----- move down for next line of block text -----

nDown += nAdTextHeight

oPPCthis := PaperCoords{nDown,nAcross}

NEXT

RETURN

METHOD Block(oPPC AS PaperCoords, aText AS STRING[], nHeightFactor AS Double) AS VOID

LOCAL i, nLen AS INT

LOCAL cLine AS STRING

LOCAL nDown, nAcross, nAdTextHeight AS Double

LOCAL oPPCthis AS PaperCoords

oPPCthis := oPPC

nDown := oPPCthis:nDown

nAcross := oPPCthis:nAcross

nLen := aText:Length

nAdTextHeight := SELF:TextHeight("PLQjy")*nHeightFactor

//nAdTextHeight := SELF:TextHeight("PLQjy")

FOR i := 1 UPTO nLen

cLine := aText[i]

SELF:Text(oPPCthis, cLine)

//----- move down for next line of block text -----

nDown += nAdTextHeight

oPPCthis := PaperCoords{nDown,nAcross}

NEXT

RETURN

METHOD BlockRestrictRHS(oPGC AS PageCoords, aText AS STRING[], nAcrossRHS AS Double) AS VOID

LOCAL i, nLen AS INT

LOCAL cLine AS STRING

LOCAL nLine, nAcross AS Double

LOCAL oPGCthis AS PageCoords

oPGCthis := oPGC

nLine := oPGCthis:nLine

nAcross := oPGCthis:nAcross

nLen := aText:Length

FOR i := 1 UPTO nLen

cLine := aText[i]

cLine := TextRestrictRHS(nAcross, cLine, nAcrossRHS)

SELF:Text(oPGCthis, cLine)

nLine +=1

oPGCthis := PageCoords{nLine,nAcross}

NEXT

SELF:NextLine := (INT)Math.Round(nLine + 0.5)

RETURN

METHOD BlockRestrictRHS(oPPC AS PaperCoords, aText AS STRING[], nAcrossRHS AS Double) AS VOID

LOCAL i, nLen AS INT

LOCAL cLine AS STRING

LOCAL nDown, nAcross, nAdTextHeight AS Double

LOCAL oPPCthis AS PaperCoords

oPPCthis := oPPC

nDown := oPPCthis:nDown

nAcross := oPPCthis:nAcross

nLen := aText:Length

nAdTextHeight := SELF:TextHeight("PLQjy")

FOR i := 1 UPTO nLen

cLine := aText[i]

cLine := TextRestrictRHS(nAcross, cLine, nAcrossRHS)

SELF:Text(oPPCthis, cLine)

//----- move down for next line of block text -----

nDown += nAdTextHeight

oPPCthis := PaperCoords{nDown,nAcross}

NEXT

RETURN

METHOD BlockRestrictRHS(oPPC AS PaperCoords, aText AS STRING[], nAcrossRHS AS Double, nHeightFactor AS Double) AS VOID

LOCAL i, nLen AS INT

LOCAL cLine AS STRING

LOCAL nDown, nAcross, nAdTextHeight AS Double

LOCAL oPPCthis AS PaperCoords

oPPCthis := oPPC

nDown := oPPCthis:nDown

nAcross := oPPCthis:nAcross

nLen := aText:Length

nAdTextHeight := SELF:TextHeight("PLQjy")*nHeightFactor

FOR i := 1 UPTO nLen

cLine := aText[i]

cLine := TextRestrictRHS(nAcross, cLine, nAcrossRHS)

SELF:Text(oPPCthis, cLine)

//----- move down for next line of block text -----

nDown += nAdTextHeight

oPPCthis := PaperCoords{nDown,nAcross}

NEXT

RETURN

METHOD BlockRight(oPGC AS PageCoords, aText AS STRING[]) AS VOID

LOCAL i, nLen AS INT

LOCAL cLine AS STRING

LOCAL nLine, nAcross AS Double

LOCAL oPGCthis AS PageCoords

oPGCthis := oPGC

nLine := oPGCthis:nLine

nAcross := oPGCthis:nAcross

nLen := aText:Length

FOR i := 1 UPTO nLen

cLine := aText[i]

SELF:TextRight(oPGCthis, cLine)

nLine +=1

oPGCthis := PageCoords{nLine,nAcross}

NEXT

SELF:NextLine := (INT)Math.Round(nLine + 0.5)

RETURN

METHOD BlockRightRestrictRHS(oPGC AS PageCoords, aText AS STRING[], nAcrossRHS AS Double) AS VOID

RETURN

METHOD BlockRightRestrictRHS(oPGC AS PageCoords, aText AS STRING[], nAcrossRHS AS Double, nHeightFactor AS Double) AS VOID

RETURN

METHOD BlockRight(oPPC AS PaperCoords, aText AS STRING[]) AS VOID

LOCAL i, nLen AS INT

LOCAL cLine AS STRING

LOCAL nDown, nAcross, nAdTextHeight AS Double

LOCAL oPPCthis AS PaperCoords

oPPCthis := oPPC

nDown := oPPCthis:nDown

nAcross := oPPCthis:nAcross

nLen := aText:Length

nAdTextHeight := SELF:TextHeight("PLQjy")

FOR i := 1 UPTO nLen

cLine := aText[i]

SELF:TextRight(oPPCthis, cLine)

//----- move down for next line of block text -----

nDown += nAdTextHeight

oPPCthis := PaperCoords{nDown,nAcross}

NEXT

RETURN

METHOD BlockRightRestrictRHS(oPPC AS PaperCoords, aText AS STRING[], nAcrossRHS AS Double) AS VOID

LOCAL i, nLen AS INT

LOCAL cLine AS STRING

LOCAL nDown, nAcross, nAdTextHeight, nWidth AS Double

LOCAL oPPCthis AS PaperCoords

LOCAL nLine, nAdjust AS Double

oPPCthis := oPPC

nAdjust := 1.0

nDown := oPPCthis:nDown

nAcross := oPPCthis:nAcross

nWidth := nAcrossRHS-nAcross

nLen := aText:Length

nAdTextHeight := SELF:TextHeight("PLQjy")

FOR i := 1 UPTO nLen

cLine := aText[i]

nLine := PCTextLength(cLine)

IF !nLine > nWidth

oPPCthis := PaperCoords{nDown,nAcross+(nWidth-nLine)}

SELF:Text(oPPCthis, cLine)

ENDIF

//----- move down for next line of block text -----

nDown += nAdTextHeight

NEXT

RETURN

METHOD BlockRightRestrictRHS(oPPC AS PaperCoords, aText AS STRING[], nAcrossRHS AS Double, nHeightFactor AS Double) AS VOID

LOCAL i, nLen AS INT

LOCAL cLine AS STRING

LOCAL nDown, nAcross, nAdTextHeight, nWidth AS Double

LOCAL oPPCthis AS PaperCoords

LOCAL nLine, nAdjust AS Double

oPPCthis := oPPC

nAdjust := 1.0

nDown := oPPCthis:nDown

nAcross := oPPCthis:nAcross

nWidth := nAcrossRHS-nAcross

nLen := aText:Length

nAdTextHeight := SELF:TextHeight("PLQjy")

FOR i := 1 UPTO nLen

cLine := aText[i]

nLine := PCTextLength(cLine)

IF !nLine > nWidth

oPPCthis := PaperCoords{nDown,nAcross+(nWidth-nLine)}

SELF:Text(oPPCthis, cLine)

ENDIF

//----- move down for next line of block text -----

nDown += nAdTextHeight*nHeightFactor

NEXT

RETURN

METHOD BlockTextDecimal(oPGC AS PageCoords, cText AS STRING, nDec AS INT, nWide AS Double) AS VOID

LOCAL r8Len, nAcross, nLine, r4Half, r8Start AS Double

LOCAL i, iX, iY, INewX, nNewAcross AS INT

LOCAL cRight, cLeft AS STRING

LOCAL aParts AS STRING[]

IF cText:Trim():Length < 1

RETURN

endif

local result as double

if ! Double.TryParse(cText, ref result)

return

endif

IF ! (result > 0 .or. result < 0)

SELF:ColTextLeft(oPGC, cText, nWide)

RETURN

endif

/*IF ! (Convert.ToDouble(cText) > 0 .or. Convert.ToDouble(cText) < 0)

SELF:ColTextLeft(oPGC, cText, nWide)

RETURN

endif */

//=============================================================

IF SELF:DecimalAligns:Length < 4

SELF:FillDecimalAlign()

ENDIF

//------------------------------------

nAcross := oPGC:nAcross

nLine := oPGC:nLine

aParts := STRING[]{2}

cText := cText:Trim()

r8Len := SELF:DecimalAligns[nDec]

r8Start := nAcross+(nWide-r8Len)

aParts := SELF:GetDecimalParts(cText, nDec)

cRight := "."+aParts[2]:Trim()

oPGC := PageCoords{nLine, r8Start}

SELF:ColTextLeft(oPGC, cRight, nWide)

cLeft := aParts[1]:Trim()

r8Len := SELF:PCTextLength(cLeft)

r8Start -= r8Len-0.5 //----- al dente -----

oPGC := PageCoords{nLine, r8Start}

SELF:ColTextLeft(oPGC, cLeft, nWide)

RETURN

METHOD BlockTextDecimal(oPPC AS PaperCoords, cText AS STRING, nDec AS INT, nWide AS Double) AS VOID

LOCAL oPGC AS PageCoords

oPGC := SELF:ConvertPaperCoords2PageCoords(oPPC)

SELF:BlockTextDecimal(oPGC, cText, nDec, nWide)

RETURN

METHOD BlockDecimal(oPGC AS PageCoords, aText AS STRING[], nDec AS INT, nWidth AS Double) AS VOID

LOCAL i, nLen AS INT

LOCAL cLine AS STRING

LOCAL nLine, nAcross AS Double

LOCAL oPGCthis AS PageCoords

oPGCthis := oPGC

nLine := oPGCthis:nLine

nAcross := oPGCthis:nAcross

nLen := aText:Length

FOR i := 1 UPTO nLen

cLine := aText[i]

SELF:BlockTextDecimal(oPGCthis, cLine, nDec, nWidth)

nLine +=1

oPGCthis := PageCoords{nLine,nAcross}

NEXT

SELF:NextLine := (INT)Math.Round(nLine + 0.5)

RETURN

METHOD BlockDecimal(oPGC AS PageCoords, aText AS STRING[], nDec AS INT, nWidth AS Double, nHeightFactor AS Double) AS VOID

LOCAL i, nLen AS INT

LOCAL cLine AS STRING

LOCAL nLine, nAcross AS Double

LOCAL oPGCthis AS PageCoords

oPGCthis := oPGC

nLine := oPGCthis:nLine

nAcross := oPGCthis:nAcross

nLen := aText:Length

FOR i := 1 UPTO nLen

cLine := aText[i]

SELF:BlockTextDecimal(oPGCthis, cLine, nDec, nWidth)

nLine +=1*nHeightFactor

oPGCthis := PageCoords{nLine,nAcross}

NEXT

SELF:NextLine := (INT)Math.Round(nLine + 0.5)

RETURN

METHOD BlockDecimal(oPPC AS PaperCoords, aText AS STRING[], nDec AS INT, nWidth AS Double) AS VOID

LOCAL i, nLen AS INT

LOCAL cLine AS STRING

LOCAL nDown, nAcross AS Double

LOCAL oPPCthis AS PaperCoords

oPPCthis := oPPC

nDown := oPPCthis:nDown

nAcross := oPPCthis:nAcross

nLen := aText:Length

FOR i := 1 UPTO nLen

cLine := aText[i]

SELF:BlockTextDecimal(oPPCthis, cLine, nDec, nWidth)

nDown += SELF:TextHeight("AQTjpy")

oPPCthis := PaperCoords{nDown,nAcross}

NEXT

RETURN

METHOD BlockDecimal(oPPC AS PaperCoords, aText AS STRING[], nDec AS INT, nWidth AS Double, nHeightFactor AS Double) AS VOID

LOCAL i, nLen AS INT

LOCAL cLine AS STRING

LOCAL nDown, nAcross AS Double

LOCAL oPPCthis AS PaperCoords

oPPCthis := oPPC

nDown := oPPCthis:nDown

nAcross := oPPCthis:nAcross

nLen := aText:Length

FOR i := 1 UPTO nLen

cLine := aText[i]

SELF:BlockTextDecimal(oPPCthis, cLine, nDec, nWidth)

nDown += SELF:TextHeight("AQTjpy")*nHeightFactor

oPPCthis := PaperCoords{nDown,nAcross}

NEXT

RETURN

METHOD BlockCenter(oPGC AS PageCoords, aText AS STRING[]) AS VOID

RETURN

METHOD BlockCenterRestrictRHS(oPGC AS PageCoords, aText AS STRING[], nAcrossRHS AS Double) AS VOID

RETURN

METHOD BlockCenter(oPPC1 AS PaperCoords, aText AS STRING[], oPPC2 AS PaperCoords) AS VOID

LOCAL i, nLen AS INT

LOCAL cLine AS STRING

LOCAL nDown, nAcross1, nAcross2, nDiff AS Double

LOCAL oPPCthis1, oPPCthis2 AS PaperCoords

oPPCthis1 := oPPC1

oPPCthis2 := oPPC2

nDown := oPPCthis1:nDown

nAcross1 := oPPCthis1:nAcross

nAcross2 := oPPCthis2:nAcross

nDiff := SELF:TextHeight("AQTjyp")

nLen := aText:Length

FOR i := 1 UPTO nLen

cLine := aText[i]

SELF:TextCenterBetween(oPPCthis1, oPPCthis2, cLine)

nDown += nDiff

oPPCthis1 := PaperCoords{nDown,nAcross1}

oPPCthis2 := PaperCoords{nDown,nAcross2}

NEXT

RETURN

METHOD BlockCenterRestrictRHS(oPPC AS PaperCoords, aText AS STRING[], nAcrossRHS AS Double) AS VOID

RETURN

METHOD BlockCenterRestrictRHS(oPPC AS PaperCoords, aText AS STRING[], nAcrossRHS AS Double, nHeightFactor AS Double) AS VOID

RETURN

METHOD BlockUnder(oPGC AS PageCoords, aText AS STRING[]) AS VOID

LOCAL i, nLen AS INT

LOCAL cLine AS STRING

LOCAL nLine, nAcross AS Double

LOCAL oPGCthis AS PageCoords

oPGCthis := oPGC

nLine := oPGCthis:nLine

nAcross := oPGCthis:nAcross

nLen := aText:Length

FOR i := 1 UPTO nLen

cLine := aText[i]

SELF:TextUnder(oPGCthis, cLine)

nLine +=1

oPGCthis := PageCoords{nLine,nAcross}

NEXT

SELF:NextLine := (INT)Math.Round(nLine + 0.5)

RETURN

METHOD BlockUnderRestrictRHS(oPGC AS PageCoords, aText AS STRING[], nAcrossRHS AS Double) AS VOID

RETURN

METHOD BlockUnder(oPPC AS PaperCoords, aText AS STRING[]) AS VOID

LOCAL i, nLen AS INT

LOCAL cLine AS STRING

LOCAL nDown, nAcross, nDiff AS Double

LOCAL oPPCthis AS PaperCoords

oPPCthis := oPPC

nDown := oPPCthis:nDown

nAcross := oPPCthis:nAcross

nDiff := SELF:TextHeight("AQTjyp")

nLen := aText:Length

FOR i := 1 UPTO nLen

cLine := aText[i]

SELF:TextUnder(oPPCthis, cLine)

nDown += nDiff

oPPCthis := PaperCoords{nDown,nAcross}

NEXT

RETURN

METHOD BlockUnder(oPPC AS PaperCoords, aText AS STRING[], nHeightFactor AS Double) AS VOID

LOCAL i, nLen AS INT

LOCAL cLine AS STRING

LOCAL nDown, nAcross, nDiff AS Double

LOCAL oPPCthis AS PaperCoords

oPPCthis := oPPC

nDown := oPPCthis:nDown

nAcross := oPPCthis:nAcross

nDiff := SELF:TextHeight("AQTjyp")*nHeightFactor

nLen := aText:Length

FOR i := 1 UPTO nLen

cLine := aText[i]

SELF:TextUnder(oPPCthis, cLine)

nDown += nDiff

oPPCthis := PaperCoords{nDown,nAcross}

NEXT

RETURN

METHOD BlockUnderRestrictRHS(oPPC AS PaperCoords, aText AS STRING[], nAcrossRHS AS Double, nHeightFactor AS Double) AS VOID

RETURN

METHOD TextUnder(oPGC AS PageCoords, cText AS STRING) AS VOID

LOCAL oPoint AS Point

LOCAL nLine AS Double

oPoint := ConvertPageCoords2Point(oPGC)

nLine := oPGC:nLine

SELF:TextUnder(oPoint, cText)

SELF:NextLine := (INT)Math.Round(nLine + 1.5)

RETURN

METHOD TextUnder(oPPC AS PaperCoords, cText AS STRING) AS VOID

LOCAL oPoint AS Point

oPoint := ConvertPaperCoords2Point(oPPC)

SELF:TextUnder(oPoint, cText)

RETURN

METHOD TextUnder(oGC AS GraphCoords, cText AS STRING) AS VOID

LOCAL oPPC AS PaperCoords

oPPC := ConvertGraphCoords2PaperCoords(oGC)

LOCAL oPoint AS Point

oPoint := ConvertPaperCoords2Point(oPPC)

SELF:TextUnder(oPoint, cText)

RETURN

METHOD TextUnder(oPoint AS Point, cText AS STRING) AS VOID

SELF:g:DrawString(cText , SELF:Font , SELF:Brush , oPoint)

LOCAL nWidth AS Double

nWidth := SELF:PCTextLength(cText)

LOCAL cUnder AS STRING

LOCAL r4Length AS Double

cUnder := "_"

r4Length := SELF:PCTextLength(cUnder)

DO WHILE r4Length < nWidth

cUnder += "_"

r4Length := SELF:PCTextLength(cUnder)

ENDDO

SELF:g:DrawString(cUnder , SELF:Font , SELF:Brush , oPoint)

RETURN

METHOD TextCenterPage(oPGC AS PageCoords, cText AS STRING) AS VOID

LOCAL oPoint AS Point

LOCAL nLine, nAcross AS double

nLine := oPGC:nLine

nAcross := SELF:FindCenterStart(cText)

oPGC := PageCoords{nLine, nAcross}

oPoint := ConvertPageCoords2Point(oPGC)

SELF:g:DrawString(cText , SELF:Font , SELF:Brush , oPoint)

SELF:NextLine := (INT)Math.Round(nLine + 1.5)

RETURN

METHOD TextCenterPage(oPPC AS PaperCoords, cText AS STRING) AS VOID

LOCAL oPoint AS Point

LOCAL nDown, nAcross AS double

nDown := oPPC:nDown

nAcross := SELF:FindCenterStart(cText)

oPPC := PaperCoords{nDown, nAcross}

oPoint := ConvertPaperCoords2Point(oPPC)

SELF:g:DrawString(cText , SELF:Font , SELF:Brush , oPoint)

RETURN

METHOD TextCenterPage(oGC AS GraphCoords, cText AS STRING) AS VOID

LOCAL oPoint AS Point

LOCAL nUp, nAcross AS Double

nUp := oGC:nUp

nAcross := SELF:FindCenterStart(cText)

oGC := GraphCoords{nAcross, nUp}

LOCAL oPPC AS PaperCoords

oPPC := ConvertGraphCoords2PaperCoords(oGC)

oPoint := ConvertPaperCoords2Point(oPPC)

SELF:g:DrawString(cText , SELF:Font , SELF:Brush , oPoint)

RETURN

METHOD TextCenterBetween(oPPC1 AS PaperCoords, oPPC2 AS PaperCoords, cText AS STRING) AS VOID

LOCAL oPoint AS Point

LOCAL nDown, nAcross, nDown1, nAcross1, nDown2, nAcross2 AS Double

LOCAL nDiff, nDiff2, nWidth, nLenText, nStart AS double

LOCAL oPPC AS PaperCoords

nDown1 := oPPC1:nDown

nDown2 := oPPC2:nDown

IF ! nDown1 = nDown2

RETURN

ENDIF

nAcross1 := oPPC1:nAcross

nAcross2 := oPPC2:nAcross

nWidth := nAcross2 - nAcross1

IF nWidth <= 0

RETURN

ENDIF

cText := cText:Trim()

nLenText := SELF:PCTextLength(cText)

IF nLenText > nWidth

RETURN

ENDIF

//=====================================

nDiff := nWidth-nLenText

nDiff2 := nDiff/2.0

nStart := nAcross1+nDiff2

nDown := oPPC1:nDown

nAcross := nStart

oPPC := PaperCoords{nDown, nAcross}

oPoint := ConvertPaperCoords2Point(oPPC)

SELF:g:DrawString(cText , SELF:Font , SELF:Brush , oPoint)

RETURN

METHOD TextCenterBetween(oPGC1 AS PageCoords, oPGC2 AS PageCoords, cText AS STRING) AS VOID

LOCAL oPPC1, oPPC2 AS PaperCoords

oPPC1 := SELF:ConvertPageCoords2PaperCoords(oPGC1)

oPPC2 := SELF:ConvertPageCoords2PaperCoords(oPGC2)

SELF:TextCenterBetween(oPPC1, oPPC2, cText)

RETURN

METHOD TextCenterBetween(oGC1 AS GraphCoords, oGC2 AS GraphCoords, cText AS STRING) AS VOID

LOCAL oPPC1, oPPC2 AS PaperCoords

oPPC1 := SELF:ConvertGraphCoords2PaperCoords(oGC1)

oPPC2 := SELF:ConvertGraphCoords2PaperCoords(oGC2)

SELF:TextCenterBetween(oPPC1, oPPC2, cText)

RETURN

METHOD TextCenterPageUnder(oPGC AS PageCoords, cText AS STRING) AS VOID

LOCAL nLine, nAcross AS double

nLine := oPGC:nLine

nAcross := SELF:FindCenterStart(cText)

oPGC := PageCoords{nLine, nAcross}

SELF:TextUnder(oPGC, cText)

SELF:NextLine := (INT)Math.Round(nLine + 1.5)

RETURN

METHOD TextCenterPageUnder(oPPC AS PaperCoords, cText AS STRING) AS VOID

LOCAL nDown, nAcross AS double

nDown := oPPC:nDown

nAcross := SELF:FindCenterStart(cText)

oPPC := PaperCoords{nDown, nAcross}

SELF:TextUnder(oPPC, cText)

RETURN

METHOD TextCenterPageUnder(oGC AS GraphCoords, cText AS STRING) AS VOID

LOCAL nUp, nAcross AS Double

nUp := oGC:nUp

nAcross := SELF:FindCenterStart(cText)

oGC := GraphCoords{nAcross, nUp}

LOCAL oPPC AS PaperCoords

oPPC := ConvertGraphCoords2PaperCoords(oGC)

SELF:TextUnder(oPPC, cText)

RETURN

METHOD FindCenterStart(cText AS STRING) AS Double

LOCAL nResult AS Double

nResult := 50

LOCAL r8LeftBit, r8CenterBit, r8Hundred AS Double

IF cText:Length < 1

RETURN nResult

ENDIF

r8Hundred := 99.9999

r8CenterBit := SELF:PCTextLength(cText)

r8LeftBit := 0.5*(r8Hundred-r8CenterBit)

nResult := Math.Round(r8LeftBit+0.5)

RETURN nResult

METHOD DrawLine(oPoint1 AS Point, oPoint2 AS Point, nPenWidth AS Double) AS VOID

LOCAL oThisPen AS Pen

oThisPen := SELF:Pen

oThisPen:Width := (Single)nPenWidth

SELF:g:DrawLine(oThisPen, oPoint1, oPoint2)

RETURN

METHOD DrawLine(oPGC1 AS PageCoords, oPGC2 AS PageCoords, nPenWidth AS Double) AS VOID

LOCAL oPoint1, oPoint2 AS Point

oPoint1 := ConvertPageCoords2Point(oPGC1)

oPoint2 := ConvertPageCoords2Point(oPGC2)

SELF:DrawLine(oPoint1, oPoint2, nPenWidth)

RETURN

METHOD DrawLine(oPPC1 AS PaperCoords, oPPC2 AS PaperCoords, nPenWidth AS Double) AS VOID

LOCAL oPoint1, oPoint2 AS Point

oPoint1 := ConvertPaperCoords2Point(oPPC1)

oPoint2 := ConvertPaperCoords2Point(oPPC2)

SELF:DrawLine(oPoint1, oPoint2, nPenWidth)

RETURN

METHOD DrawLine(oGC1 AS GraphCoords, oGC2 AS GraphCoords, nPenWidth AS Double) AS VOID

LOCAL oPPC1, oPPC2 AS PaperCoords

oPPC1 := ConvertGraphCoords2PaperCoords(oGC1)

oPPC2 := ConvertGraphCoords2PaperCoords(oGC2)

LOCAL oPoint1, oPoint2 AS Point

oPoint1 := ConvertPaperCoords2Point(oPPC1)

oPoint2 := ConvertPaperCoords2Point(oPPC2)

SELF:DrawLine(oPoint1, oPoint2, nPenWidth)

RETURN

METHOD MemoPlace(oPGC AS PageCoords, cText AS STRING, nWide AS Double) AS VOID

LOCAL aText AS STRING[]

aText := Memo2Lines(cText, nWide)

SELF:Block(oPGC, aText)

RETURN

METHOD MemoPlace(oPPC AS PaperCoords, cText AS STRING, nWide AS Double) AS VOID

LOCAL aText AS STRING[]

aText := Memo2Lines(cText, nWide)

SELF:Block(oPPC, aText)

RETURN

METHOD MemoPlace(oGC AS GraphCoords, cText AS STRING, nWide AS Double) AS VOID

LOCAL aText AS STRING[]

LOCAL oPPC AS PaperCoords

oPPC := ConvertGraphCoords2PaperCoords(oGC)

aText := Memo2Lines(cText, nWide)

SELF:Block(oPPC, aText)

RETURN

METHOD MemoPlaceFullyJustify(oPGC AS PageCoords, cText AS STRING, nWide AS Double) AS VOID

LOCAL aText AS STRING[]

aText := Memo2LinesSameLength(cText, nWide)

SELF:Block(oPGC, aText)

RETURN

METHOD MemoPlaceFullyJustify(oPPC AS PaperCoords, cText AS STRING, nWide AS Double) AS VOID

LOCAL aText AS STRING[]

aText := Memo2LinesSameLength(cText, nWide)

SELF:Block(oPPC, aText)

RETURN

METHOD MemoPlaceFullyJustify(oGC AS GraphCoords, cText AS STRING, nWide AS Double) AS VOID

LOCAL aText AS STRING[]

LOCAL oPPC AS PaperCoords

oPPC := ConvertGraphCoords2PaperCoords(oGC)

aText := Memo2LinesSameLength(cText, nWide)

SELF:Block(oPPC, aText)

RETURN

METHOD PageUpdate() AS VOID

IF SELF:AutoPage

SELF:NumberOfPages +=1

ENDIF

RETURN

METHOD PCTextLength(cText AS STRING) AS Double

LOCAL fResult, fWidth AS Double

fWidth := SELF:TextLength(cText)

fResult := 99.9999*fWidth/SELF:PageWidth

RETURN fResult

METHOD Words(cGivenString AS STRING) AS STRING[]

LOCAL iP1, iP2, i, nLen AS INT

LOCAL cLline, cRline, cRest, cText AS STRING

LOCAL aWords, aRest AS STRING[]

LOCAL alWords AS ArrayList

alWords := ArrayList{}

cGivenString := cGivenString:Trim()

//---------------- check for length -----------------

IF cGivenString:Length < 1

aWords := NULL_OBJECT

RETURN aWords

ENDIF

//------------------------ check for no spaces ------------------------

IF ! cGivenString:Contains(" ")

aWords := STRING[]{1}

aWords[1] := cGivenString

RETURN aWords

ENDIF

iP1 := cGivenString:IndexOf(" ")

alWords:Add(cGivenString:Substring(0,iP1+1):Trim())

cRline := cGivenString:Substring(iP1+1):Trim()

cRest := ""

IF ! cRline:Contains(" ")

alWords:Add(cRline)

ELSE

aRest := SELF:Words(cRline) //--- recursive call to method ---

ENDIF

IF ! aRest = NULL_OBJECT

nLen := aRest:Length

FOR i := 1 UPTO nLen

cText := aRest[i]:Trim()

alWords:Add(cText)

NEXT

ENDIF

aWords := (STRING[])alWords:ToArray(typeof(STRING))

RETURN aWords

METHOD WordsList(cGivenString AS STRING) AS ArrayList

LOCAL iP1, iP2, i, nLen AS INT

LOCAL cLline, cRline, cRest, cText AS STRING

LOCAL aWords, aRest AS STRING[]

LOCAL alWords AS ArrayList

alWords := ArrayList{}

cGivenString := cGivenString:Trim()

//---------------- check for length -----------------

IF cGivenString:Length < 1

RETURN alWords

ENDIF

//------------------------ check for no spaces ------------------------

IF ! cGivenString:Contains(" ")

alWords:Add(cGivenString)

RETURN alWords

ENDIF

iP1 := cGivenString:IndexOf(" ")

alWords:Add(cGivenString:Substring(0,iP1+1):Trim())

cRline := cGivenString:Substring(iP1+1):Trim()

cRest := ""

IF ! cRline:Contains(" ")

alWords:Add(cRline)

ELSE

aRest := SELF:Words(cRline) //--- recursive call to method ---

ENDIF

IF ! aRest = NULL_OBJECT

nLen := aRest:Length

FOR i := 1 UPTO nLen

cText := aRest[i]:Trim()

alWords:Add(cText)

NEXT

ENDIF

RETURN alWords

METHOD Memo2Lines(cText AS STRING, nWide AS Double) AS STRING[]

LOCAL aLines, aWords AS STRING[]

LOCAL alLines, alWords AS ArrayList

LOCAL i, nLen AS INT

LOCAL qWords AS Queue

LOCAL cWord, cLine, cNewLine AS STRING

LOCAL nThisWide AS Double

alWords := SELF:WordsList(cText)

qWords := Queue{alWords}

nLen := qWords:Count

alLines := ArrayList{}

cLine := ""

DO WHILE .T.

IF qWords:Count < 1

IF cLine:Length > 0

alLines:Add(cLine)

cLine := ""

ENDIF

EXIT

ENDIF

//----------------------------

cWord := (STRING)qWords:Peek()

IF cLine:Length > 0

cNewLine := cLine + " "+ cWord

ELSE

cNewLine := cWord

ENDIF

nThisWide := SELF:PCTextLength(cNewLine)

IF nThisWide < nWide

cLine := cNewLine

qWords:Dequeue()

LOOP

ENDIF

IF nThisWide = nWide

cLine += cNewLine

qWords:Dequeue()

alLines:Add(cLine)

cLine := ""

LOOP

ENDIF

alLines:Add(cLine)

cLine := ""

ENDDO

aLines := (STRING[])alLines:ToArray(typeof(STRING))

RETURN aLines

METHOD Memo2LinesSameLength(cText AS STRING, nWide AS Double) AS STRING[]

LOCAL aLines, aWords AS STRING[]

LOCAL alLines, alWords AS ArrayList

LOCAL i, nLen AS INT

LOCAL qWords AS Queue

LOCAL cWord, cLine, cNewLine AS STRING

LOCAL nThisWide AS Double

alWords := SELF:WordsList(cText)

qWords := Queue{alWords}

nLen := qWords:Count

alLines := ArrayList{}

cLine := ""

DO WHILE .T.

IF qWords:Count < 1

IF cLine:Length > 0

alLines:Add(cLine)

cLine := ""

ENDIF

EXIT

ENDIF

//----------------------------

cWord := (STRING)qWords:Peek()

IF cLine:Length > 0

cNewLine := cLine + " "+ cWord

ELSE

cNewLine := cWord

ENDIF

nThisWide := SELF:PCTextLength(cNewLine)

IF nThisWide < nWide

cLine := cNewLine

qWords:Dequeue()

LOOP

ENDIF

IF nThisWide = nWide

cLine += cNewLine

qWords:Dequeue()

alLines:Add(Line2SpecifiedLength(cLine, nWide))

cLine := ""

LOOP

ENDIF

alLines:Add(Line2SpecifiedLength(cLine, nWide))

cLine := ""

ENDDO

aLines := (STRING[])alLines:ToArray(typeof(STRING))

RETURN aLines

METHOD Line2SpecifiedLength(cLine AS STRING, nWide AS Double) AS STRING

LOCAL cResult, cNewLine AS STRING

LOCAL aGaps AS STRING[]

LOCAL nLen, nPointer, i AS INT

LOCAL nThisWide AS Double

nThisWide := SELF:PCTextLength(cLine)

IF nThisWide >= nWide

cResult := cLine

RETURN cResult

ENDIF

IF ! cLine:Contains(" ")

cResult := cLine

RETURN cResult

ENDIF

LOCAL aWords AS STRING[]

aWords := SELF:Words(cLine)

nLen := aWords:Length

aGaps := STRING[]{nLen-1} //----- telegraph poles and gaps / spaces thing ! -----

FOR i := 1 UPTO nLen-1

aGaps[i] := " "

NEXT

nPointer := 1

DO WHILE .T.

cNewLine := SELF:ConcatLine(aWords, aGaps)

nThisWide := SELF:PCTextLength(cNewLine)

IF nThisWide = nWide

EXIT

ENDIF

IF nThisWide > nWide

/*cLine := aGaps[nPointer]

nLen := cLine:Length

aGaps[nPointer] := cLine:Substring(0,nLen-1) */

EXIT

ENDIF

nPointer +=1

IF nPointer > nLen-1

nPointer := 1

ENDIF

aGaps[nPointer] += " "

ENDDO

cResult := SELF:ConcatLine(aWords, aGaps)

RETURN cResult

METHOD ConcatLine(aWords AS STRING[], aGaps AS STRING[]) AS STRING

LOCAL cResult AS STRING

LOCAL i, nLen AS INT

cResult := ""

nLen := aWords:Length

FOR i := 1 UPTO nLen-1

cResult += aWords[i]+aGaps[i]

NEXT

cResult += aWords[nLen]

RETURN cResult

METHOD VLineSet(oPGC AS PageCoords, N AS INT, x AS Double, y2 AS Double) AS VOID

//---- oPGC (x1,y1) is top left (PAGE) coords, N number of lines, x spacing ----

//------------ y2 is bottom limit (so length is y2 - oPGC:nLine) ----

LOCAL i AS INT

LOCAL x1, y1 AS Double

LOCAL lError AS LOGIC

lError := FALSE

x1 := oPGC:nAcross

y1 := oPGC:nLine

IF y1 >= y2

lError := TRUE

ENDIF

IF N < 2

lError := TRUE

ENDIF

IF x <= 0

lError := TRUE

ENDIF

IF (N-1)*x+x1 > 100

lError := TRUE

ENDIF

IF lError

Messagebox.Show("error in VLineSet ???")

SELF:Error := TRUE

SELF:LastErrorMessage := "VLineSet not specified correctly."

RETURN

ENDIF

//------------------------------------------

LOCAL oPoint1, oPoint2 AS Point

LOCAL oPGCnew AS PageCoords

FOR i := 0 UPTO N-1

oPGCnew := PageCoords{y1, x1+i*x}

oPoint1 := ConvertPageCoords2Point(oPGCnew)

oPGCnew := PageCoords{y2, x1+i*x}

oPoint2 := ConvertPageCoords2Point(oPGCnew)

SELF:g:DrawLine(SELF:Pen, oPoint1, oPoint2)

NEXT

lError := FALSE

RETURN

METHOD VLineSet(oPPC AS PaperCoords, N AS INT, x AS Double, y2 AS Double) AS VOID

//---- oPPC (x1,y1) is top left (PAPER) coords, N number of lines, x spacing ----

//------------ y2 is bottom limit (so length is y2 - oPGC:nLine) ----

LOCAL i AS INT

LOCAL x1, y1 AS Double

LOCAL lError AS LOGIC

lError := FALSE

x1 := oPPC:nAcross

y1 := oPPC:nDown

IF y1 >= y2

lError := TRUE

ENDIF

IF N < 2

lError := TRUE

ENDIF

IF x <= 0

lError := TRUE

ENDIF

IF (N-1)*x+x1 > 100

lError := TRUE

ENDIF

IF lError

Messagebox.Show("error in VLineSet ???")

SELF:Error := TRUE

SELF:LastErrorMessage := "VLineSet not specified correctly."

RETURN

ENDIF

//------------------------------------------

LOCAL oPoint1, oPoint2 AS Point

LOCAL oPPCnew AS PaperCoords

FOR i := 0 UPTO N-1

oPpCnew := PaperCoords{y1, x1+i*x}

oPoint1 := ConvertPaperCoords2Point(oPPCnew)

oPPCnew := PaperCoords{y2, x1+i*x}

oPoint2 := ConvertPaperCoords2Point(oPPCnew)

SELF:g:DrawLine(SELF:Pen, oPoint1, oPoint2)

NEXT

lError := FALSE

RETURN

METHOD VLineSet(oGC AS GraphCoords, N AS INT, x AS Double, y2 AS Double) AS VOID

//---- oGC (x1,y1) is top left (GRAPH) coords, N number of lines, x spacing ----

//------------ y2 is bottom limit (so length is y2 - oGC:nUp) ----

LOCAL oPPC AS PaperCoords

oPPC := ConvertGraphCoords2PaperCoords(oGC)

SELF:VLineSet(oPPC, N, x, y2)

RETURN

METHOD VLineSetComplex(oPGC AS PageCoords, aX AS Double[], y2 AS Double) AS VOID

//--------- oPGC is top left (PAGE) coords, N number of lines, -----------

//--------- aX is a set of spacings in the x direction -------------

//------------ y2 is bottom limit (so length is y2 - oPGC:nLine) ----

LOCAL i, iLen, N AS INT

LOCAL x1, y1, xG, xSum AS Double

LOCAL lError AS Boolean

lError := FALSE

N := aX:Length

x1 := oPGC:nAcross

y1 := oPGC:nLine

IF y1 >= y2

lError := TRUE

ENDIF

IF N < 1

lError := TRUE

ENDIF

xSum := 0.0

FOR i := 1 UPTO N

xSum += aX[i]

NEXT

IF xSum+x1 > 100

lError := TRUE

ENDIF

IF lError

//Messagebox.Show("error in VLineSetComplex ???")

SELF:Error := TRUE

SELF:LastErrorMessage := "VLineSetComplex not specified correctly."

RETURN

ENDIF

//-----------------------

LOCAL oPoint1, oPoint2 AS Point

LOCAL oPGCnew AS PageCoords

xG := x1

oPGCnew := PageCoords{y1, xG}

oPoint1 := ConvertPageCoords2Point(oPGCnew)

oPGCnew := PageCoords{y2, xG}

oPoint2 := ConvertPageCoords2Point(oPGCnew)

SELF:g:DrawLine(SELF:Pen, oPoint1, oPoint2)

FOR i := 1 UPTO N

xG += aX[i]

oPGCnew := PageCoords{y1, xG}

oPoint1 := ConvertPageCoords2Point(oPGCnew)

oPGCnew := PageCoords{y2, xG}

oPoint2 := ConvertPageCoords2Point(oPGCnew)

SELF:g:DrawLine(SELF:Pen, oPoint1, oPoint2)

NEXT

lError := FALSE

RETURN

METHOD HLineSet(oPGC AS PageCoords, N AS INT, y AS Double, x2 AS Double) AS VOID

//----- oPGC (x1,y1) is top left (PAGE) coords, N number of lines, y spacing -----

//------------ x2 is right limit (so length is x2 - oPGC:nAcross) -----

LOCAL i AS INT

LOCAL x1, y1 AS Double

LOCAL lError AS LOGIC

lError := FALSE

x1 := oPGC:nAcross

y1 := oPGC:nLine

IF x1 >= x2

lError := TRUE

ENDIF

IF N < 2

lError := TRUE

ENDIF

IF y <= 0

lError := TRUE

ENDIF

IF (N-1)*y+y1 > 100

lError := TRUE

ENDIF

IF lError

//Messagebox.Show("error in HLineSet ???")

SELF:Error := TRUE

SELF:LastErrorMessage := "HLineSet not specified correctly."

RETURN

ENDIF

//-----------------------

LOCAL oPoint1, oPoint2 AS Point

LOCAL oPGCnew AS PageCoords

FOR i := 0 UPTO N-1

oPGCnew := PageCoords{y1+i*y, x1}

oPoint1 := ConvertPageCoords2Point(oPGCnew)

oPGCnew := PageCoords{y1+i*y, x2}

oPoint2 := ConvertPageCoords2Point(oPGCnew)

SELF:g:DrawLine(SELF:Pen, oPoint1, oPoint2)

NEXT

lError := FALSE

RETURN

METHOD HLineSet(oPPC AS PaperCoords, N AS INT, y AS Double, x2 AS Double) AS VOID

//----- oPPC (x1,y1) is top left (PAPER) coords, N number of lines, y spacing -----

//------------ x2 is right limit (so length is x2 - oPPC:nAcross) -----

LOCAL i AS INT

LOCAL x1, y1 AS Double

LOCAL lError AS LOGIC

lError := FALSE

x1 := oPPC:nAcross

y1 := oPPC:nDown

IF x1 >= x2

lError := TRUE

ENDIF

IF N < 2

lError := TRUE

ENDIF

IF y <= 0

lError := TRUE

ENDIF

IF (N-1)*y+y1 > 100

lError := TRUE

ENDIF

IF lError

//Messagebox.Show("error in HLineSet ???")

SELF:Error := TRUE

SELF:LastErrorMessage := "HLineSet not specified correctly."

RETURN

ENDIF

//-----------------------

LOCAL oPoint1, oPoint2 AS Point

LOCAL oPPCnew AS PaperCoords

FOR i := 0 UPTO N-1

oPPCnew := PaperCoords{y1+i*y, x1}

oPoint1 := ConvertPaperCoords2Point(oPPCnew)

oPPCnew := PaperCoords{y1+i*y, x2}

oPoint2 := ConvertPaperCoords2Point(oPPCnew)

SELF:g:DrawLine(SELF:Pen, oPoint1, oPoint2)

NEXT

lError := FALSE

RETURN

METHOD HLineSet(oGC AS GraphCoords, N AS INT, y AS Double, x2 AS Double) AS VOID

//----- oGC (x1,y1) is top left (GRAPH) coords, N number of lines, y spacing -----

//------------ x2 is right limit (so length is x2 - oGC:nAcross) -----

LOCAL oPPC AS PaperCoords

oPPC := ConvertGraphCoords2PaperCoords(oGC)

SELF:HLineSet(oPPC, N, y, x2)

RETURN

METHOD HLineSetComplex(oPGC AS PageCoords, aY AS Double[], x2 AS Double) AS VOID

//---- oPGC is top left (PAGE) coords, aY the array of spaces ------

//------------ x2 is right limit (so length is x2 - oPGC:nAcross) ----

LOCAL i, N AS INT

LOCAL x1, y1, ySum, yG AS Double

LOCAL lError AS Boolean

lError := FALSE

x1 := oPGC:nAcross

y1 := oPGC:nLine

N := aY:Length

IF x1 >= x2

lError := TRUE

ENDIF

IF N < 1

lError := TRUE

ENDIF

ySum := 0.0

FOR i := 1 UPTO N

ySum += aY[i]

NEXT

IF ySum+y1 > SELF:LinesPerPage

lError := TRUE

ENDIF

IF lError

//Messagebox.Show("error in HLineSetComplex ???")

SELF:Error := TRUE

SELF:LastErrorMessage := "HLineSetComplex not specified correctly."

RETURN

ENDIF

//-----------------------

LOCAL oPoint1, oPoint2 AS Point

LOCAL oPGCnew AS PageCoords

yG := y1

oPGCnew := PageCoords{yG, x1}

oPoint1 := ConvertPageCoords2Point(oPGCnew)

oPGCnew := PageCoords{yG, x2}

oPoint2 := ConvertPageCoords2Point(oPGCnew)

SELF:g:DrawLine(SELF:Pen, oPoint1, oPoint2)

FOR i := 1 UPTO N

yG += aY[i]

oPGCnew := PageCoords{yG, x1}

oPoint1 := ConvertPageCoords2Point(oPGCnew)

oPGCnew := PageCoords{yG, x2}

oPoint2 := ConvertPageCoords2Point(oPGCnew)

SELF:g:DrawLine(SELF:Pen, oPoint1, oPoint2)

NEXT

lError := FALSE

RETURN

METHOD GridRegularNbyM(oPGC1 AS PageCoords, oPGC2 AS PageCoords, N AS INT, M AS INT) AS VOID

LOCAL x1, x2, y1, y2 AS Double

LOCAL xDiff, yDiff, xInc, yInc AS Double

x1 := oPGC1:nAcross

y1 := oPGC1:nLine

x2 := oPGC2:nAcross

y2 := oPGC2:nLine

xDiff := x2-x1

yDiff := y2-y1

xInc := xDiff/M

yInc := yDiff/N

SELF:VLineSet(oPGC1, M+1, xInc, y2)

SELF:HLineSet(oPGC1, N+1, yInc, x2)

RETURN

METHOD GridRegularNbyMX(oPPC1 AS PaperCoords, oPPC2 AS PaperCoords, N AS INT, M AS INT, Q AS Boolean) AS VOID

//----- top left to bottom right -----

LOCAL x1, x2, y1, y2 AS Double

LOCAL xDiff, yDiff, xInc, yInc AS Double

x1 := oPPC1:nAcross

y1 := oPPC1:nDown

x2 := oPPC2:nAcross

y2 := oPPC2:nDown

xDiff := x2-x1

yDiff := y2-y1

xInc := xDiff/M

yInc := yDiff/N

SELF:VLineSet(oPPC1, M+1, xInc, y2)

SELF:HLineSet(oPPC1, N+1, yInc, x2)

RETURN

METHOD GridRegularNbyM(oPPC1 AS PaperCoords, oPPC2 AS PaperCoords, N AS INT, M AS INT) AS VOID

LOCAL oPGC1, oPGC2 AS PageCoords

oPGC1 := ConvertPaperCoords2PageCoords(oPPC1)

oPGC2 := ConvertPaperCoords2PageCoords(oPPC2)

SELF:GridRegularNbyM(oPGC1, oPGC2, N,M)

RETURN

METHOD GridRegularNbyM(oGC1 AS GraphCoords, oGC2 AS GraphCoords, N AS INT, M AS INT) AS VOID

//----- top left to bottom right -----

LOCAL oPPC1, oPPC2 AS PaperCoords

oPPC1 := ConvertGraphCoords2PaperCoords(oGC1)

oPPC2 := ConvertGraphCoords2PaperCoords(oGC2)

SELF:GridRegularNbyM(oPPC1, oPPC2, N, M)

RETURN

METHOD GridComplex(oPGC AS PageCoords, aX AS Double[], aY AS Double[]) AS VOID

//---- top left - rectangular grid - (PAGE coords) ------

//---- bottom right determined by spacings total -----

//---- aX is a set of spacings for vertical lines -----

//---- aY is a set of spacing for horizontal lines -----

//---- number of lines determined by arrays ----

LOCAL i, N, M AS INT

LOCAL x1, y1 AS Double

LOCAL x2, y2 AS Double

x1 := oPGC:nAcross

y1 := oPGC:nLine

N := aY:Length

M := aX:Length

x2 := x1

FOR i := 1 UPTO (M)

x2 += aX[i]

NEXT

y2 := y1

FOR i := 1 UPTO (N)

y2 += aY[i]

NEXT

SELF:VLineSetComplex(oPGC, aX, y2)

SELF:HLineSetComplex(oPGC, aY, x2)

//------ what of closing last line - to do a neat job ? -------

//------ could miss last from iterator and add calculated ! ---

RETURN

METHOD GridComplexX(oPPC AS PaperCoords, aX AS Double[], aY AS Double[], Q AS Boolean) AS VOID

//---- top left - rectangular grid - (PAGE coords) ------

//---- bottom right determined by spacings total -----

//---- aX is a set of spacings for vertical lines -----

//---- aY is a set of spacing for horizontal lines -----

//---- number of lines determined by arrays ----

/*LOCAL i, N, M AS INT

LOCAL x1, y1 AS Double

LOCAL x2, y2 AS Double

x1 := oPGC:nAcross

y1 := oPGC:nLine

N := aY:Length

M := aX:Length

x2 := x1

FOR i := 1 UPTO (M)

x2 += aX[i]

NEXT

y2 := y1

FOR i := 1 UPTO (N)

y2 += aY[i]

NEXT

SELF:VLineSetComplex(oPPC, aX, y2)

SELF:HLineSetComplex(oPPC, aY, x2)

*/

//------ what of closing last line - to do a neat job ? -------

//------ could miss last from iterator and add calculated ! ---

RETURN

METHOD GridComplex(oPPC AS PaperCoords, aX AS Double[], aY AS Double[]) AS VOID

LOCAL oPGC AS PageCoords

oPGC := SELF:ConvertPaperCoords2PageCoords(oPPC)

SELF:GridComplex(oPGC, aX, aY)

RETURN

METHOD BitmapPlace(oPGC AS PageCoords, cBitMapPath AS STRING) AS VOID

LOCAL oMyImage AS Bitmap

LOCAL oPoint AS Point

LOCAL oMyFile AS FileInfo

oMyFile := FileInfo{cBitMapPath}

IF ! oMyFile:Exists

oMyFile := NULL_OBJECT

RETURN

ENDIF

//-----------------------------------------------

oMyImage := Bitmap{cBitMapPath}

oPoint := ConvertPageCoords2Point(oPGC)

SELF:g:DrawImage(oMyImage, oPoint)

oMyImage:Dispose()

RETURN

METHOD BitmapPlace(oPPC AS PaperCoords, cBitMapPath AS STRING) AS VOID

LOCAL oMyImage AS Bitmap

LOCAL oPoint AS Point

LOCAL oMyFile AS FileInfo

oMyFile := FileInfo{cBitMapPath}

IF ! oMyFile:Exists

oMyFile := NULL_OBJECT

RETURN

ENDIF

//-----------------------------------------------

oMyImage := Bitmap{cBitMapPath}

oPoint := ConvertPaperCoords2Point(oPPC)

SELF:g:DrawImage(oMyImage, oPoint)

oMyImage:Dispose()

RETURN

METHOD BitmapPlace(oGC AS GraphCoords, cBitMapPath AS STRING) AS VOID

LOCAL oMyImage AS Bitmap

LOCAL oPoint AS Point

LOCAL oMyFile AS FileInfo

oMyFile := FileInfo{cBitMapPath}

IF ! oMyFile:Exists

oMyFile := NULL_OBJECT

RETURN

ENDIF

//-----------------------------------------------

oMyImage := Bitmap{cBitMapPath}

LOCAL oPPC AS PaperCoords

oPPC := ConvertGraphCoords2PaperCoords(oGC)

oPoint := ConvertPaperCoords2Point(oPPC)

SELF:g:DrawImage(oMyImage, oPoint)

oMyImage:Dispose()

RETURN

METHOD BitmapPlaceScaled(oPGC AS PageCoords, cBitMapPath AS STRING, nFactor AS Double) AS VOID

LOCAL oMyImage, oMyImage2 AS Bitmap

LOCAL oPoint AS Point

LOCAL oMyFile AS FileInfo

LOCAL nWidth, nHeight AS INT

LOCAL oSize AS Size

oMyFile := FileInfo{cBitMapPath}

IF ! oMyFile:Exists

oMyFile := NULL_OBJECT

RETURN

ENDIF

//-----------------------------------------------

oMyImage := Bitmap{cBitMapPath}

oPoint := ConvertPageCoords2Point(oPGC)

oSize := oMyImage:Size

nWidth := oSize:Width

nHeight := oSize:Height

oMyImage2 := Bitmap{oMyImage, (INT)Math.Round(nFactor*nWidth), (INT)Math.Round(nFactor*nHeight)}

SELF:g:DrawImage(oMyImage2, oPoint)

oMyImage:Dispose()

oMyImage2:Dispose()

RETURN

METHOD BitmapPlaceScaled(oPPC AS PaperCoords, cBitMapPath AS STRING, nFactor AS Double) AS VOID

LOCAL oMyImage, oMyImage2 AS Bitmap

LOCAL oPoint AS Point

LOCAL oMyFile AS FileInfo

LOCAL nWidth, nHeight AS INT

LOCAL oSize AS Size

oMyFile := FileInfo{cBitMapPath}

IF ! oMyFile:Exists

oMyFile := NULL_OBJECT

RETURN

ENDIF

//-----------------------------------------------

oMyImage := Bitmap{cBitMapPath}

oPoint := ConvertPaperCoords2Point(oPPC)

oSize := oMyImage:Size

nWidth := oSize:Width

nHeight := oSize:Height

oMyImage2 := Bitmap{oMyImage, (INT)Math.Round(nFactor*nWidth), (INT)Math.Round(nFactor*nHeight)}

SELF:g:DrawImage(oMyImage2, oPoint)

oMyImage:Dispose()

oMyImage2:Dispose()

RETURN

METHOD BitmapPlaceScaled(oGC AS GraphCoords, cBitMapPath AS STRING, nFactor AS Double) AS VOID

LOCAL oMyImage, oMyImage2 AS Bitmap

LOCAL oPoint AS Point

LOCAL oMyFile AS FileInfo

LOCAL nWidth, nHeight AS INT

LOCAL oSize AS Size

oMyFile := FileInfo{cBitMapPath}

IF ! oMyFile:Exists

oMyFile := NULL_OBJECT

RETURN

ENDIF

//-----------------------------------------------

oMyImage := Bitmap{cBitMapPath}

LOCAL oPPC AS PaperCoords

oPPC := ConvertGraphCoords2PaperCoords(oGC)

oPoint := ConvertPaperCoords2Point(oPPC)

oSize := oMyImage:Size

nWidth := oSize:Width

nHeight := oSize:Height

oMyImage2 := Bitmap{oMyImage, (INT)Math.Round(nFactor*nWidth), (INT)Math.Round(nFactor*nHeight)}

SELF:g:DrawImage(oMyImage2, oPoint)

oMyImage:Dispose()

oMyImage2:Dispose()

RETURN

METHOD BitmapPlaceAtBottomLeft(oPGC AS PageCoords, cBitMapPath AS STRING) AS VOID

LOCAL oMyImage AS Image

LOCAL oPoint AS Point

LOCAL oMyFile AS FileInfo

LOCAL oSize AS Size

LOCAL nWidth, nHeight AS INT

LOCAL X,Y AS INT

oMyFile := FileInfo{cBitMapPath}

IF ! oMyFile:Exists

oMyFile := NULL_OBJECT

RETURN

ENDIF

//-----------------------------------------------

oMyImage := Bitmap{cBitMapPath}

oSize := oMyImage:Size

nWidth := oSize:Width

nHeight := oSize:Height

oPoint := ConvertPageCoords2Point(oPGC)

X := oPoint:X

Y := oPoint:Y

oPoint := Point{X, Y-nHeight}

SELF:g:DrawImage(oMyImage,oPoint)

oMyImage:Dispose()

RETURN

METHOD BitmapPlaceTransparent(oPGC AS PageCoords, cBitMapPath AS STRING) AS VOID

LOCAL oBitmap AS Bitmap

LOCAL oPoint AS Point

LOCAL oMyFile AS FileInfo

oMyFile := FileInfo{cBitMapPath}

IF ! oMyFile:Exists

oMyFile := NULL_OBJECT

RETURN

ENDIF

//-----------------------------------------------

oBitmap := Bitmap{cBitMapPath}

oBitmap:MakeTransparent(Color.White)

oPoint := ConvertPageCoords2Point(oPGC)

SELF:g:DrawImage(oBitmap, oPoint)

oBitmap:Dispose()

RETURN

METHOD BitmapPlaceTransparent(oPGC AS PageCoords, cBitMapPath AS STRING, oColor AS Color) AS VOID

LOCAL oBitmap AS Bitmap

LOCAL oPoint AS Point

LOCAL oMyFile AS FileInfo

oMyFile := FileInfo{cBitMapPath}

IF ! oMyFile:Exists

oMyFile := NULL_OBJECT

RETURN

ENDIF

//-----------------------------------------------

oBitmap := Bitmap{cBitMapPath}

oBitmap:MakeTransparent(oColor)

oPoint := ConvertPageCoords2Point(oPGC)

SELF:g:DrawImage(oBitmap, oPoint)

oBitmap:Dispose()

RETURN

METHOD GetLeftDecimal(cText AS STRING) AS STRING

LOCAL cLeft AS STRING

LOCAL nIndex AS INT

cLeft := ""

nIndex := cText:IndexOf(".")

IF nIndex > 1

cLeft := cText:Substring(0,nIndex):Trim()

ENDIF

//Messagebox.Show(cLeft:ToString())

RETURN cLeft

METHOD GetDecimalParts(cText AS STRING, nDec AS INT) AS STRING[]

LOCAL cLeft, cRight AS STRING

LOCAL nIndex AS INT

LOCAL aParts AS STRING[]

aParts := <STRING>{"",""}

cLeft := ""

cRight := "00000"

IF !cText:Contains(".")

cLeft := cText:Trim()

ELSE

nIndex := cText:IndexOf(".")

cLeft := cText:Substring(0,nIndex):Trim()

cRight := cText:Substring(nIndex+1):Trim()+"00000"

//cRight := cRight:Substring(0,nDec)

ENDIF

cRight := cRight:Substring(0,nDec)

//Messagebox.Show(cLeft:ToString())

//Messagebox.Show(cRight:ToString())

aParts := <STRING>{cLeft, cRight}

RETURN aParts

METHOD FillDecimalAlign() AS VOID

LOCAL i AS INT

LOCAL cRight , cText AS STRING

LOCAL r8Len AS Double

SELF:DecimalAligns := Double[]{4}

cRight := ""

FOR i := 1 UPTO 4

cRight += "0"

cText := "."+cRight

r8Len := SELF:PCTextLength(cText)

SELF:DecimalAligns[i] := r8Len

NEXT

RETURN

METHOD ColTextLeft(oPGC AS PageCoords, cText AS STRING, nWide AS Double) AS VOID

LOCAL nLine, nAcross AS Double

LOCAL oPoint AS Point

LOCAL nY, nX AS Double

LOCAL iY, iX AS INT

LOCAL cLine AS STRING

IF cText:Trim():Length < 1

RETURN

ENDIF

//----- chop line if too long ! -----

cLine := SELF:TextRestrictRHS(cText, nWide)

nLine := oPGC:nLine

nAcross := oPGC:nAcross

IF nLine < 1 .or. nLine > SELF:LinesPerPage

//--------- warn box ---------

nLine := 1

ENDIF

IF nAcross < 0 .or. nAcross > 100

//---------- warn box ---------

nAcross := 0.0

ENDIF

oPoint := ConvertPageCoords2Point(oPGC)

SELF:g:DrawString(cLine , SELF:Font , SELF:Brush , oPoint)

RETURN

METHOD ColTextCenter(oPGC AS PageCoords, cText AS STRING, nWide AS Double)AS VOID

LOCAL r8Len, nAcross, nLine, r8Half, r8Start, r8Remain AS Double

cText := cText:Trim()

r8Len := SELF:PCTextLength(cText)

nAcross := oPGC:nAcross

nLine := oPGC:nLine

r8Remain := nWide - r8Len

IF r8Remain <= 0

SELF:ColTextLeft(oPGC, cText, nWide)

RETURN

ENDIF

//-----------------------------------------

r8Half := r8Remain*0.5

r8Start := nAcross+r8Half

oPGC := PageCoords{nLine, r8Start}

SELF:ColTextLeft(oPGC, cText, nWide)

RETURN

METHOD ColTextRight(oPGC AS PageCoords, cText AS STRING, nWide AS Double) AS VOID

LOCAL r8Len, nAcross, nLine, r8Start AS Double

LOCAL cLine AS STRING

nAcross := oPGC:nAcross

nLine := oPGC:nLine

cText := cText:Trim()

r8Len := SELF:PCTextLength(cText)

IF r8Len > nWide

//----- chop line if too long ! -----

cLine := SELF:Line2SpecifiedLength(cText, nWide)

r8Len := SELF:PCTextLength(cLine) //----- ought to be nWide ! -----

ELSE

cLine := cText

ENDIF

r8Start := nAcross + nWide - r8Len

oPGC := PageCoords{nLine, r8Start}

SELF:ColTextLeft(oPGC, cLine, nWide)

RETURN

METHOD ColTextDecimal(oPGC AS PageCoords, cText AS STRING, nDec AS INT, nWide AS Double) AS VOID

LOCAL r8Len, nAcross, nLine, r4Half, r8Start AS Double

LOCAL i, iX, iY, INewX, nNewAcross AS INT

LOCAL cRight, cLeft AS STRING

LOCAL aParts AS STRING[]

IF SELF:DecimalAligns:Length < 4

SELF:FillDecimalAlign()

ENDIF

//------------------------------------

nAcross := oPGC:nAcross

nLine := oPGC:nLine

aParts := STRING[]{2}

cText := cText:Trim()

r8Len := SELF:DecimalAligns[nDec]

r8Start := nAcross+(nWide-r8Len)

aParts := SELF:GetDecimalParts(cText, nDec)

cRight := "."+aParts[2]:Trim()

oPGC := PageCoords{nLine, r8Start}

SELF:ColTextLeft(oPGC, cRight, nWide)

cLeft := aParts[1]:Trim()

r8Len := SELF:PCTextLength(cLeft)

r8Start -= r8Len-0.5 //----- al dente -----

//r8Start -= r8Len-0.7 //----- al dente -----

//r8Start -= r8Len-1.0 //----- al dente -----

oPGC := PageCoords{nLine, r8Start}

SELF:ColTextLeft(oPGC, cLeft, nWide)

RETURN

END CLASS

CLASS CanvasCoords

EXPORT X, Y AS INT

EXPORT oPoint AS Point

CONSTRUCTOR(nX AS INT, nY AS INT)

SUPER()

SELF:X := nX

SELF:Y := nY

SELF:oPoint := Point{X,Y}

RETURN

END CLASS

CLASS PageCoords

EXPORT nLine, nAcross AS Double

EXPORT LinesPerPage AS INT

CONSTRUCTOR(nY AS Double, nX AS Double)

SUPER()

SELF:LinesPerPage := 0

SELF:nLine := nY

SELF:nAcross := nX

RETURN

CONSTRUCTOR(nLPP AS INT, nY AS Double, nX AS Double)

SUPER()

SELF:LinesPerPage := nLPP

SELF:nLine := nY

SELF:nAcross := nX

RETURN

END CLASS

CLASS PaperCoords

EXPORT nDown, nAcross AS Double

CONSTRUCTOR(nY AS Double, nX AS Double)

SUPER()

SELF:nDown := nY

SELF:nAcross := nX

RETURN

END CLASS

CLASS GraphCoords

EXPORT nUp, nAcross AS Double

CONSTRUCTOR(nX AS Double, nY AS Double)

SUPER()

SELF:nUp := nY

SELF:nAcross := nX

RETURN

END CLASS

//===========================================

END NAMESPACE // ClassLibraryBESTdocument

=====================================================================================

PHH - 17th. March 2018.


the TestBed applet - what is it's purpose ?

Main Purpose :

The main purpose of a 'TestBed', as far as engineers are concerned, is to try out newly designed and

created items.

In our case the 'item' for testing is a code method which produces a 'Print' file.

The print file is shown in a print 'Preview' control (or dialog form) and we can study as to whether or not

it looks like we would wish it to look when we coded the method.

In producing a suitable coded method, I find myself going round the cycle quite a few times - many!

So we code some lines, then compile and run, see if things are okay or need changing, then keep

going with the development cycle.

So to summarise, the 'TestBed' helps us do our design for the document being created.

A 'document' is a general name, as we can be designing a whole range of possible printed items.

A report is just one of those possible printed formats.

Multiple pages can be seen in the 'TestBed' applet.

Secondary Purpose :

Because there are sample document methods built by myself into the TestBed applet, we can also

use the working app to see both what is possible, as well as how to do it in code.

Out of interest, you may like to know that I did this sort of thing myself, while writing these eNotes, and

re-coding with X# the 'TestBed' you see in this section.

It was many years since I had done much coding for document methods, so my skills were just a little

rusty - okay, I had forgotten a lot ;-0)

PHH - 25th. March 2018.


How to use the TestBed applet ...

This is quite easy really.

Load the app into Visual Studio and compile and run it.

With the main form / window on screen, try out one by one the different buttons - each one

shows a document of a different format.

Once you are happy with running the app, and handling the preview control and dialog, go to

the VS environment and look at the code provided in the document methods for each button option.

PHH - 25th. March 2018.


Navigation: 'Printing and Printers', and Report generation too ... > the 'TestBed' app for printing with the 'BestDocument' Class >

Return to chapter overview

How to create a new document for yourself ...

PHH - 25th. March 2018.


What do you require from printing and reporting ?

You the developer :

Your customers / users :

Style of printed output :

Third Party providers of a software tool :

Asking others who have tried things out :

PHH - 29th. December 2017.


Preview tools in WPF apps ...

========================================================================================================================

PHH - 23rd. March 2018.


Accessing Printers

For any guy trying to do their own printing in Visual Objects (using Win32) finding and selecting a

Windows printer was not easy.

If I remember right then guys at DevFest would offer code 'chunks' and personal downloads, to help

each other - most of us never knew what the code was doing, just that it found a list of printers.

Well, in the world of .NET this task is a synch (dead easy), as in fact it should be if any Framework

is designed and written properly.

This was one thing that encouraged me to venture into the world of .NET - I have to be honest, I was

hoping to make an excuse to myself for staying with VO - saying that if I could not get my printing

class working in Vulcan.NET then I would NOT go the .NET way.

Little did I know that only two to three days later I would be up and running - amazing!

=========================================================================

===============================================================================================================================

PHH - April 11th. 2017


'DIY' Printing - an X# class library - 'BESTdocument'

What do I mean by 'DIY' printing ?

Well 'DIY' is what it usually means in 'street parlance'," Do It Yourself".

So we have (or create) a DLL which is designed to support printing various types of element on

a page, or series of pages.

If we handle printing in this 'DIY' approach then we have to do everything ourselves, 'from the bottom

up' as it were.

By approaching printing in this way we at least get to know how Microsoft have been handling printing

for quite some time.

Microsoft have provided the basic software tools that we can use (or tap into) and these are the same

basic tools that Third Party companies use to create their expense ? Reporting frameworks.

I call them 'expensive' because they are not cheap - BUT - usually they offer a very good product with

many, many, features and much functionality.

My personal thoughts are that I am pleased I designed and created my own Printer class, simply

because I now understand more of what is going on - HOWEVER - I would now exclusively use the

third party tools that can be purchased for .NET, WPF and Reporting. They are wonderful.

So a product like that from DevExpress (DX) is both a joy to use as well as doing MUCH that is just

over the heads of most local developers.

In my eyes, the time of 'DIY' printing is past, gone for ever I hope.

Having said all that, let us learn some of the background 'stuff' to printing in the .NET Framework.

PHH - April 11th. 2017


some Background to the 'BESTdocument' class ...

The 'BESTdocument' class of mine is based on the .NET class called 'PrintDocument'.

My company was called 'BEST consultancy' - for "Business and Education Services and Training".

So 'BEST' had nothing to do with me thinking I was any better than the next man in the street ;-0)

Here is the head of my .PRG file for the Class Library :-

==================================================================

So what in .NET is the PrintDocument class all about ? - Checkout below :-

[ .. from Help Viewer 2.2 .. ]

======================================================================

Notice the introduction of the idea that one graphics related object holds the details for a single page.

The 'reusable' idea means to us that we have to create one page at a time, then have it printed, and to

keep going as long as there are other pages to be printed.

Lets look at some more .NET details :-

=============================================================

Yes, we need to use the System.Drawing.Printing namespace.

And, the 'PrintPage' we see in my code throughout these related sub-sections, refers to an Event, and not a method.

Checkout these details :-

=================================================================================================================

So lets see some more information about 'PrintPage' - check below :-

=============================================================================================================================

To understand the above details better, or more thoroughly, it will be easier to see

the way I have gotten code to work in a simple sample, or two ;-0)

Yes, this technology has been around since the dawn of .NET - yes, and it still works just fine.

Checkout the supporting Framework version numbers :-

=============================================

PHH - 23rd. March 2018.


working code ...

=================================================================================

=================================================================================

========================================================================================================================================

=================================================================================

USING System

USING System.Collections.Generic

USING System.Linq

USING System.Text

//

// Class1.prg

//

//----- copied from VIDE development project -----

//-----

// Application : BestDocument

// BestDocument.prg , Started-Created : 16/08/2008 08:59:12 AM

USING System.Windows.Forms

USING System.Drawing

USING System.Drawing.Printing

USING System.IO

USING System.Drawing.Drawing2D

USING System.Collections

USING System.Data

USING Microsoft.VisualBasic

Using System.Xml.Serialization

BEGIN NAMESPACE ClassLibraryBESTdocument

CLASS BestDocument INHERIT PrintDocument

EXPORT JobName AS STRING

EXPORT Font AS Font

EXPORT Brush AS Brush

EXPORT Pen AS Pen

EXPORT Color AS Color

EXPORT Size AS Double

EXPORT NextLine AS INT

EXPORT Landscape AS Boolean

EXPORT LinesPerPage, PageNumber, NumberOfPages, FirstPage, LastPage AS INT

EXPORT MyPrinterName AS STRING

EXPORT AutoPage AS Boolean

EXPORT g, Graphics AS Graphics

EXPORT PrinterName AS STRING

PROTECT HardMarginX, HardMarginY AS Double

PROTECT CanvasLength, CanvasWidth AS Double

PROTECT PCCL, PCCW AS Double //------- percentage canvas length/.......

PROTECT PCPL, PCPW AS Double //------- percentage page length/........

PROTECT RM, LM, TM, BM AS Double //-------- margins Right/Left/......

PROTECT PCLM, PCRM, PCTM, PCBM AS Double //-------- % margins Right/Left/......

PROTECT BLP, BRP, TLP, TRP AS Point //---- bottom left/ ......

PROTECT PageLength, PageWidth AS Double

PROTECT LineHeightP AS Double //-------- vertical distance between Page lines (Portrait)---------

PROTECT LineHeightL AS Double //-------- vertical distance between Page lines (Landscape) ---------

PROTECT LineHeight AS Double //-------- one used for actual printing ---------

EXPORT Error AS Boolean

EXPORT LastErrorMessage AS STRING

EXPORT DecimalAligns AS Double[]

EXPORT oPageSettings AS PageSettings

EXPORT cDefaultPrinter AS STRING

EXPORT nPenWidth AS Double

CONSTRUCTOR(cJobName AS STRING, cPrinterName AS STRING)

SUPER()

cPrinterName := cPrinterName:Trim()

IF cPrinterName == ""

LOCAL oPS AS PrinterSettings

oPS := PrinterSettings{}

SELF:cDefaultPrinter := oPS:PrinterName

cPrinterName := SELF:cDefaultPrinter

ENDIF

SELF:MyPrinterName := cPrinterName

SELF:PrinterName := cPrinterName

SELF:JobName := cJobName:Trim()

SELF:PrinterSettings:PrinterName :=cPrinterName

SELF:Initialize()

RETURN

METHOD Initialize() AS VOID

SELF:Landscape := FALSE

SELF:LinesPerPage := 60 //--- default setting ---

SELF:NextLine := 1 //--- could this be zero ? ---

//----- set default font, brush and pen -----

SELF:Font := Font{"Arial",12}

SELF:Brush := SolidBrush{Color.Black}

SELF:Pen := Pen{SELF:Brush, 1.0}

SELF:Color := Color.Black

DecimalAligns := Double[]{1}

RETURN

METHOD SetGraphics(g AS Graphics) AS VOID

SELF:Graphics := g

SELF:g := g

RETURN

METHOD SetPageBoundaries() AS VOID

LOCAL oPS AS PrinterSettings

LOCAL oMyArea AS RectangleF

LOCAL nHardX, nHardY AS Double

LOCAL nMLeft, nMRight, nMTop, nMBottom AS Double

LOCAL nPCMLeft, nPCMRight, nPCMTop, nPCMBottom AS Double

LOCAL nPAreaLeft, nPAreaRight, nPAreaTop, nPAreaBottom AS Double

oPS := PrinterSettings{}

oPS:PrinterName := SELF:PrinterName

//------------ need to select the printer first ---------------

IF SELF:Landscape

oPS:DefaultPageSettings:Landscape := TRUE

ELSE

oPS:DefaultPageSettings:Landscape := FALSE

ENDIF

oPageSettings := oPS:DefaultPageSettings

oMyArea := oPageSettings:PrintableArea

nHardX := oPageSettings:HardMarginX

nHardY := oPageSettings:HardMarginY

SELF:HardMarginX := nHardX

SELF:HardMarginY := nHardY

nPAreaLeft := oMyArea:Left

nPAreaRight := oMyArea:Right

nPAreaTop := oMyArea:Top

nPAreaBottom := oMyArea:Bottom

SELF:CanvasLength := nPAreaBottom - nPAreaTop

SELF:CanvasWidth := nPAreaRight - nPAreaLeft

SELF:PCCL := SELF:CanvasLength*0.01

SELF:PCCW := SELF:CanvasWidth*0.01

nPCMLeft := 2*SELF:PCCW

nPCMRight := 2*SELF:PCCW

nPCMTop := 2*SELF:PCCL

nPCMBottom := 2*SELF:PCCL

nMLeft := nPAreaLeft

nMRight := nPAreaRight

nMTop := nPAreaTop

nMBottom := nPAreaBottom

SELF:LM := nMLeft

SELF:RM := nMRight

SELF:TM := nMTop

SELF:BM := nMBottom

SELF:PCLM := nPCMLeft

SELF:PCRM := nPCMRight

SELF:PCTM := nPCMTop

SELF:PCBM := nPCMBottom

SELF:BLP := Point{(INT)Math.Floor(SELF:LM), (INT)Math.Floor(SELF:BM)}

SELF:BRP := Point{(INT)Math.Floor(SELF:CanvasWidth-SELF:LM), (INT)Math.Floor(SELF:BM)}

SELF:TLP := Point{(INT)Math.Floor(SELF:LM), (INT)Math.Floor(SELF:CanvasLength-SELF:BM)}

SELF:TRP := Point{(INT)Math.Floor(SELF:CanvasWidth-SELF:LM), (INT)Math.Floor(SELF:CanvasLength-SELF:BM)}

SELF:PageLength := SELF:CanvasLength

SELF:PageWidth := SELF:CanvasWidth

SELF:LineHeightP := SELF:PageLength/(SELF:LinesPerPage+2)//---- adjusted 'al dente' to fit the screen view ----

SELF:LineHeightL := SELF:PageWidth/(SELF:LinesPerPage+2)//---- adjusted 'al dente' to fit the screen view ----

IF oPS:DefaultPageSettings:Landscape

SELF:LineHeight := SELF:LineHeightL

SELF:PageLength := SELF:CanvasWidth

SELF:PageWidth := SELF:CanvasLength

ELSE

SELF:LineHeight := SELF:LineHeightP

ENDIF

SELF:PCPL := SELF:PageLength*0.01

SELF:PCPW := SELF:PageWidth*0.01

RETURN

METHOD ResetLinesPerPage(nLPP AS INT) AS VOID

IF nLPP < 20

nLPP := 20

ENDIF

IF nLPP > 130

nLPP := 130

ENDIF

SELF:LinesPerPage := nLPP

SELF:LineHeight := SELF:PageLength/(SELF:LinesPerPage+2)//---- adjusted 'al dente' to fit the screen view ----

RETURN

METHOD ResetOrientation(cOrient AS STRING) AS VOID

IF cOrient:ToUpper():Trim() == "L"

SELF:Landscape := TRUE

SELF:DefaultPageSettings:Landscape := TRUE

ELSE

SELF:Landscape := FALSE

SELF:DefaultPageSettings:Landscape := FALSE

ENDIF

RETURN

METHOD ReSetBrushAndPen() AS VOID

SELF:ApplyPenWdith(1.0)

RETURN

METHOD ApplyPenWdith(nWidth AS Double) AS VOID

SELF:nPenWidth := nWidth

SELF:Brush := SolidBrush{SELF:Color}

SELF:Pen := Pen{SELF:Brush, (Single)SELF:nPenWidth}

RETURN

METHOD ApplyNewColor(oColor AS Color) AS VOID

SELF:Color := oColor

SELF:Brush := SolidBrush{SELF:Color}

SELF:Pen := Pen{SELF:Brush, (Single)SELF:nPenWidth}

RETURN

METHOD SetNextLine(nNext AS INT) AS VOID

IF nNext < 1

nNext := 1

ENDIF

IF nNext > SELF:NextLine

nNext := SELF:NextLine

ENDIF

SELF:NextLine := nNext

RETURN

METHOD GetMargins() AS Double[]

LOCAL aMargins AS Double[]

aMargins := Double[]{4}

aMargins[1] := SELF:LM

aMargins[2] := SELF:RM

aMargins[3] := SELF:TM

aMargins[4] := SELF:BM

RETURN aMargins

METHOD PrintableAreaDetails() AS STRING

LOCAL cDetails AS STRING

LOCAL cR2 AS STRING

cR2 := CRLF+CRLF

cDetails := "Details ..."+cR2

cDetails += "Printer : "+SELF:PrinterName:ToString()+cR2

cDetails += "Lines per Page : "+SELF:LinesPerPage:ToString()+cR2

cDetails += "Hard Margin X : "+SELF:HardMarginX:ToString()+CRLF

cDetails += "Hard Margin Y : "+SELF:HardMarginY:ToString()+cR2

cDetails += "Canvas Length : "+SELF:CanvasLength:ToString()+CRLF

cDetails += "Canvas Width : "+SELF:CanvasWidth:ToString()+cR2

cDetails += "Left Margin : "+SELF:LM:ToString()+CRLF

cDetails += "Right Margin : "+SELF:RM:ToString()+CRLF

cDetails += "Top Margin : "+SELF:TM:ToString()+CRLF

cDetails += "Bottom Margin : "+SELF:BM:ToString()+cR2

cDetails += "Line Height : "+SELF:LineHeight:ToString()+cR2

RETURN cDetails

METHOD TextLength(cText AS STRING) AS Double

LOCAL oSizeF AS SizeF

LOCAL fResult AS Double

LOCAL nLen AS INT

nLen := cText:Length

fResult := 0.0

IF nLen < 1

RETURN fResult

ENDIF

LOCAL oDrawFormat AS StringFormat

LOCAL oPoint AS Point

oPoint := Point{100,100} //----- less risky than 0,0 !? -----

oDrawFormat := StringFormat{}

oDrawFormat:FormatFlags := StringFormatFlags.MeasureTrailingSpaces

oSizeF := SELF:g:MeasureString(cText, SELF:Font, oPoint, oDrawFormat)

fResult := oSizeF:Width

RETURN fResult

METHOD TextHeight(cText AS STRING) AS Double

LOCAL oSizeF AS SizeF

LOCAL fResult, fHeight AS Double

oSizeF := SELF:g:MeasureString(cText, SELF:Font)

fHeight := oSizeF:Height

fResult := 100*fHeight/SELF:PageLength

RETURN fResult

METHOD ConvertGraphCoords2Point(oGC AS GraphCoords) AS Point

LOCAL oPoint AS Point

oPoint := ConvertPaperCoords2Point(ConvertGraphCoords2PaperCoords(oGC))

RETURN oPoint

METHOD ConvertPageCoords2Point(oPGC AS PageCoords) AS Point

LOCAL nLine, nAcross AS Double

LOCAL nLPP AS INT

LOCAL oPoint AS Point

LOCAL nY, nX AS Double

LOCAL iY, iX AS INT

LOCAL r8Line, r8LineHeight, r8Across, r8LM, r8PCPW AS Double

LOCAL r8TM AS Double

nLPP := SELF:LinesPerPage

r8LineHeight := SELF:LineHeight

nLine := oPGC:nLine

nAcross := oPGC:nAcross

IF nLine < 0 .or. nLine > nLPP

//--------- warn box ---------

RETURN Point{0,0}

ENDIF

IF nAcross < 0 .or. nAcross > 100

//---------- warn box ---------

RETURN Point{0,0}

ENDIF

r8TM := SELF:TM

r8Line := nLine

nY := r8TM+(r8Line)*r8LineHeight

r8LM := SELF:LM

r8Across := nAcross

r8PCPW := SELF:PCPW

nX := r8LM+r8Across*r8PCPW

iY := (INT)Math.Floor(nY+0.5)

iX := (INT)Math.Floor(nX+0.5)

oPoint := Point{iX, iY}

RETURN oPoint

METHOD ConvertPaperCoords2Point(oPPC AS PaperCoords) AS Point

LOCAL nDown, nAcross AS Double

LOCAL oPoint AS Point

LOCAL nY, nX AS Double

LOCAL iY, iX AS INT

LOCAL r8LM, r8PCPW AS Double

LOCAL r8TM, r8PCPL AS Double

nDown := oPPC:nDown

nAcross := oPPC:nAcross

IF nDown < 0 .or. nDown > 100

//--------- warn box ---------

RETURN Point{0,0}

ENDIF

IF nAcross < 0 .or. nAcross > 100

//---------- warn box ---------

RETURN Point{0,0}

ENDIF

r8TM := SELF:TM

r8PCPL := SELF:PCPL

nY := r8TM+nDown*r8PCPL

r8LM := SELF:LM

r8PCPW := SELF:PCPW

nX := r8LM+nAcross*r8PCPW

iY := (INT)Math.Floor(nY+0.5)

iX := (INT)Math.Floor(nX+0.5)

oPoint := Point{iX, iY}

RETURN oPoint

METHOD ConvertPoint2PaperCoords(oPoint AS Point) AS PaperCoords

LOCAL oPPC AS PaperCoords

LOCAL nDown, nAcross AS Double

LOCAL r8TM, r8PCPL AS Double

LOCAL r8LM, r8PCPW AS Double

LOCAL nY, nX AS Double

nX := (Double)oPoint:X

nY := (Double)oPoint:Y

r8TM := SELF:TM

r8PCPL := SELF:PCPL

nDown := (nY-r8TM)/r8PCPL

r8LM := SELF:LM

r8PCPW := SELF:PCPW

nAcross := (nX-r8LM)/r8PCPW

oPPC := PaperCoords{nDown, nAcross}

RETURN oPPC

METHOD ConvertPoint2PageCoords(oPoint AS Point) AS PageCoords

LOCAL oPGC AS PageCoords

LOCAL nY, nX AS Double

LOCAL r8LM, r8PCPW, nAcross, r8TM, nLine AS Double

nX := (Double)oPoint:X

nY := (Double)oPoint:Y

r8LM := SELF:LM

r8PCPW := SELF:PCPW

nAcross := (nX-r8LM)/r8PCPW

r8TM := SELF:TM

nLine := (nY-r8TM)/SELF:LineHeight

oPGC := PageCoords{nLine, nAcross}

RETURN oPGC

METHOD ConvertPoint2GraphCoords(oPoint AS Point) AS GraphCoords

LOCAL oGC AS GraphCoords

LOCAL oPPC AS PaperCoords

oPPC := ConvertPoint2PaperCoords(oPoint)

oGC := ConvertPaperCoords2GraphCoords(oPPC)

RETURN oGC

METHOD ConvertGraphCoords2PaperCoords(oGC AS GraphCoords) AS PaperCoords

LOCAL oPPC AS PaperCoords

oPPC := PaperCoords{100-oGC:nUp, oGC:nAcross}

RETURN oPPC

METHOD ConvertPaperCoords2GraphCoords(oPPC AS PaperCoords) AS GraphCoords

LOCAL oGC AS GraphCoords

oGC := GraphCoords{100-oPPC:nDown, oPPC:nAcross}

RETURN oGC

METHOD ConvertPageCoords2PaperCoords(oPGC AS PageCoords) AS PaperCoords

LOCAL oPPC AS PaperCoords

LOCAL oPoint AS Point

oPoint := ConvertPageCoords2Point(oPGC)

oPPC := ConvertPoint2PaperCoords(oPoint)

RETURN oPPC

METHOD ConvertPaperCoords2PageCoords(oPPC AS PaperCoords) AS PageCoords

LOCAL oPGC AS PageCoords

LOCAL oPoint AS Point

oPoint := ConvertPaperCoords2Point(oPPC)

oPGC := ConvertPoint2PageCoords(oPoint)

RETURN oPGC

METHOD Text(oPGC AS PageCoords, cText AS STRING) AS VOID

LOCAL oPoint AS Point

LOCAL nLine AS double

nLine := oPGC:nLine

oPoint := ConvertPageCoords2Point(oPGC)

SELF:g:DrawString(cText , SELF:Font , SELF:Brush , oPoint)

SELF:NextLine := (INT)Math.Round(nLine + 1.5)

RETURN

METHOD TextRestrictRHS(nAcross AS Double, cText AS STRING, nAcrossRHS AS Double) AS STRING

LOCAL cResult AS STRING

cResult := cText

IF nAcrossRHS < nAcross+5

nAcrossRHS := nAcross+5

ENDIF

LOCAL nPCtarget, nPCmax, nPCnow AS Double

nPCmax := 100-nAcross

nPCtarget := nAcrossRHS - nAcross

IF nPCtarget > nPCmax

nPCtarget := nPCmax

ENDIF

nPCnow := PCTextLength(cText)

IF nPCnow <= nPCtarget

RETURN cResult

ENDIF

LOCAL nLen AS INT

DO WHILE nPCnow > nPCtarget

nLen := cText:Length

cText := cText:Substring(0,nLen-1)

nPCnow := PCTextLength(cText)

ENDDO

cResult := cText

RETURN cResult

METHOD TextRestrictRHS(cText AS STRING, nWide AS Double) AS STRING

LOCAL cResult AS STRING

cResult := cText

IF nWide < 5

nWide := 5

ENDIF

IF nWide > 100

nWide := 100

ENDIF

LOCAL nPCtarget, nPCnow AS Double

nPCtarget := nWide

nPCnow := PCTextLength(cText)

IF nPCnow <= nPCtarget

RETURN cResult

ENDIF

LOCAL nLen AS INT

DO WHILE nPCnow > nPCtarget

nLen := cText:Length

cText := cText:Substring(0,nLen-1)

nPCnow := PCTextLength(cText)

ENDDO

cResult := cText

RETURN cResult

METHOD Text(oPPC AS PaperCoords, cText AS STRING) AS VOID

LOCAL oPoint AS Point

oPoint := ConvertPaperCoords2Point(oPPC)

SELF:g:DrawString(cText , SELF:Font , SELF:Brush , oPoint)

RETURN

METHOD Text(oGC AS GraphCoords, cText AS STRING) AS VOID

LOCAL oPoint AS Point

LOCAL oPPC AS PaperCoords

oPPC := ConvertGraphCoords2PaperCoords(oGC)

oPoint := ConvertPaperCoords2Point(oPPC)

SELF:g:DrawString(cText , SELF:Font , SELF:Brush , oPoint)

RETURN

METHOD TextChop(oPPC AS PaperCoords, cText AS STRING, nWide AS Double) AS VOID

LOCAL cResult AS STRING

cResult := SELF:TextRestrictRHS(cText, nWide)

SELF:Text(oPPC, cResult)

RETURN

METHOD TextRight(oPGC AS PageCoords, cText AS STRING) AS VOID

LOCAL oPoint AS Point

LOCAL nLine, nTextLen, nNewAcross, nAcross AS double

LOCAL oPGCnew AS PageCoords

nLine := oPGC:nLine

nAcross := oPGC:nAcross

nTextLen := SELF:PCTextLength(cText)

nNewAcross := nAcross-nTextLen

IF nNewAcross < 0

nNewAcross := 0

ENDIF

oPGCnew := PageCoords{nLine, nNewAcross}

oPoint := ConvertPageCoords2Point(oPGCnew)

SELF:g:DrawString(cText , SELF:Font , SELF:Brush , oPoint)

SELF:NextLine := (INT)Math.Round(nLine + 1.5)

RETURN

METHOD TextRight(oPPC AS PaperCoords, cText AS STRING) AS VOID

LOCAL oPoint AS Point

LOCAL nDown, nTextLen, nNewAcross, nAcross AS double

LOCAL oPPCnew AS PaperCoords

nDown := oPPC:nDown

nAcross := oPPC:nAcross

nTextLen := SELF:PCTextLength(cText)

nNewAcross := nAcross-nTextLen

IF nNewAcross < 0

nNewAcross := 0

ENDIF

oPPCnew := PaperCoords{nDown, nNewAcross}

oPoint := ConvertPaperCoords2Point(oPPCnew)

SELF:g:DrawString(cText , SELF:Font , SELF:Brush , oPoint)

RETURN

METHOD TextRight(oGC AS GraphCoords, cText AS STRING) AS VOID

LOCAL oPoint AS Point

LOCAL nDown, nTextLen, nNewAcross, nAcross AS Double

LOCAL oPPCnew, oPPC AS PaperCoords

oPPC := SELF:ConvertGraphCoords2PaperCoords(oGC)

nDown := oPPC:nDown

nAcross := oPPC:nAcross

nTextLen := SELF:PCTextLength(cText)

nNewAcross := nAcross-nTextLen

IF nNewAcross < 0

nNewAcross := 0

ENDIF

oPPCnew := PaperCoords{nDown, nNewAcross}

oPoint := ConvertPaperCoords2Point(oPPCnew)

SELF:g:DrawString(cText , SELF:Font , SELF:Brush , oPoint)

RETURN

METHOD TextUp(oPGC AS PageCoords, cText AS STRING) AS VOID

LOCAL oPoint AS Point

oPoint := ConvertPageCoords2Point(oPGC)

LOCAL oDrawFormat AS StringFormat

oDrawFormat := StringFormat{}

oDrawFormat:FormatFlags := StringFormatFlags.DirectionVertical

SELF:g:DrawString(cText , SELF:Font , SELF:Brush , oPoint, oDrawFormat )

RETURN

METHOD TextUp(oPPC AS PaperCoords, cText AS STRING) AS VOID

LOCAL oPoint AS Point

oPoint := ConvertPaperCoords2Point(oPPC)

LOCAL oDrawFormat AS StringFormat

oDrawFormat := StringFormat{}

oDrawFormat:FormatFlags := StringFormatFlags.DirectionVertical

SELF:g:DrawString(cText , SELF:Font , SELF:Brush , oPoint, oDrawFormat )

RETURN

METHOD TextUpTo(oPPC AS PaperCoords, cText AS STRING) AS VOID

LOCAL oPoint AS Point

LOCAL oSizeF AS SizeF

LOCAL nX, nY AS INT

oPoint := ConvertPaperCoords2Point(oPPC)

nX := oPoint:X

nY := oPoint:Y

LOCAL oDrawFormat AS StringFormat

oDrawFormat := StringFormat{}

oDrawFormat:FormatFlags := StringFormatFlags.DirectionVertical

oSizeF := SELF:g:MeasureString(cText, SELF:Font, oPoint, oDrawFormat)

nY -= (INT)oSizeF:Height

oPoint := Point{nX, nY}

SELF:g:DrawString(cText , SELF:Font , SELF:Brush , oPoint, oDrawFormat )

RETURN

METHOD TextUp(oGC AS GraphCoords, cText AS STRING) AS VOID

LOCAL oPoint AS Point

oPoint := ConvertGraphCoords2Point(oGC)

LOCAL oDrawFormat AS StringFormat

oDrawFormat := StringFormat{}

oDrawFormat:FormatFlags := StringFormatFlags.DirectionVertical

SELF:g:DrawString(cText , SELF:Font , SELF:Brush , oPoint, oDrawFormat )

RETURN

METHOD Block(oPGC AS PageCoords, aText AS STRING[]) AS VOID

LOCAL i, nLen AS INT

LOCAL cLine AS STRING

LOCAL nLine, nAcross AS Double

LOCAL oPGCthis AS PageCoords

oPGCthis := oPGC

nLine := oPGCthis:nLine

nAcross := oPGCthis:nAcross

nLen := aText:Length

FOR i := 1 UPTO nLen

cLine := aText[i]

SELF:Text(oPGCthis, cLine)

nLine +=1

oPGCthis := PageCoords{nLine,nAcross}

NEXT

SELF:NextLine := (INT)Math.Round(nLine + 0.5)

RETURN

METHOD Block(oPPC AS PaperCoords, aText AS STRING[]) AS VOID

LOCAL i, nLen AS INT

LOCAL cLine AS STRING

LOCAL nDown, nAcross, nAdTextHeight AS Double

LOCAL oPPCthis AS PaperCoords

oPPCthis := oPPC

nDown := oPPCthis:nDown

nAcross := oPPCthis:nAcross

nLen := aText:Length

nAdTextHeight := SELF:TextHeight("PLQjy")

FOR i := 1 UPTO nLen

cLine := aText[i]

SELF:Text(oPPCthis, cLine)

//----- move down for next line of block text -----

nDown += nAdTextHeight

oPPCthis := PaperCoords{nDown,nAcross}

NEXT

RETURN

METHOD Block(oPPC AS PaperCoords, aText AS STRING[], nHeightFactor AS Double) AS VOID

LOCAL i, nLen AS INT

LOCAL cLine AS STRING

LOCAL nDown, nAcross, nAdTextHeight AS Double

LOCAL oPPCthis AS PaperCoords

oPPCthis := oPPC

nDown := oPPCthis:nDown

nAcross := oPPCthis:nAcross

nLen := aText:Length

nAdTextHeight := SELF:TextHeight("PLQjy")*nHeightFactor

//nAdTextHeight := SELF:TextHeight("PLQjy")

FOR i := 1 UPTO nLen

cLine := aText[i]

SELF:Text(oPPCthis, cLine)

//----- move down for next line of block text -----

nDown += nAdTextHeight

oPPCthis := PaperCoords{nDown,nAcross}

NEXT

RETURN

METHOD BlockRestrictRHS(oPGC AS PageCoords, aText AS STRING[], nAcrossRHS AS Double) AS VOID

LOCAL i, nLen AS INT

LOCAL cLine AS STRING

LOCAL nLine, nAcross AS Double

LOCAL oPGCthis AS PageCoords

oPGCthis := oPGC

nLine := oPGCthis:nLine

nAcross := oPGCthis:nAcross

nLen := aText:Length

FOR i := 1 UPTO nLen

cLine := aText[i]

cLine := TextRestrictRHS(nAcross, cLine, nAcrossRHS)

SELF:Text(oPGCthis, cLine)

nLine +=1

oPGCthis := PageCoords{nLine,nAcross}

NEXT

SELF:NextLine := (INT)Math.Round(nLine + 0.5)

RETURN

METHOD BlockRestrictRHS(oPPC AS PaperCoords, aText AS STRING[], nAcrossRHS AS Double) AS VOID

LOCAL i, nLen AS INT

LOCAL cLine AS STRING

LOCAL nDown, nAcross, nAdTextHeight AS Double

LOCAL oPPCthis AS PaperCoords

oPPCthis := oPPC

nDown := oPPCthis:nDown

nAcross := oPPCthis:nAcross

nLen := aText:Length

nAdTextHeight := SELF:TextHeight("PLQjy")

FOR i := 1 UPTO nLen

cLine := aText[i]

cLine := TextRestrictRHS(nAcross, cLine, nAcrossRHS)

SELF:Text(oPPCthis, cLine)

//----- move down for next line of block text -----

nDown += nAdTextHeight

oPPCthis := PaperCoords{nDown,nAcross}

NEXT

RETURN

METHOD BlockRestrictRHS(oPPC AS PaperCoords, aText AS STRING[], nAcrossRHS AS Double, nHeightFactor AS Double) AS VOID

LOCAL i, nLen AS INT

LOCAL cLine AS STRING

LOCAL nDown, nAcross, nAdTextHeight AS Double

LOCAL oPPCthis AS PaperCoords

oPPCthis := oPPC

nDown := oPPCthis:nDown

nAcross := oPPCthis:nAcross

nLen := aText:Length

nAdTextHeight := SELF:TextHeight("PLQjy")*nHeightFactor

FOR i := 1 UPTO nLen

cLine := aText[i]

cLine := TextRestrictRHS(nAcross, cLine, nAcrossRHS)

SELF:Text(oPPCthis, cLine)

//----- move down for next line of block text -----

nDown += nAdTextHeight

oPPCthis := PaperCoords{nDown,nAcross}

NEXT

RETURN

METHOD BlockRight(oPGC AS PageCoords, aText AS STRING[]) AS VOID

LOCAL i, nLen AS INT

LOCAL cLine AS STRING

LOCAL nLine, nAcross AS Double

LOCAL oPGCthis AS PageCoords

oPGCthis := oPGC

nLine := oPGCthis:nLine

nAcross := oPGCthis:nAcross

nLen := aText:Length

FOR i := 1 UPTO nLen

cLine := aText[i]

SELF:TextRight(oPGCthis, cLine)

nLine +=1

oPGCthis := PageCoords{nLine,nAcross}

NEXT

SELF:NextLine := (INT)Math.Round(nLine + 0.5)

RETURN

METHOD BlockRightRestrictRHS(oPGC AS PageCoords, aText AS STRING[], nAcrossRHS AS Double) AS VOID

RETURN

METHOD BlockRightRestrictRHS(oPGC AS PageCoords, aText AS STRING[], nAcrossRHS AS Double, nHeightFactor AS Double) AS VOID

RETURN

METHOD BlockRight(oPPC AS PaperCoords, aText AS STRING[]) AS VOID

LOCAL i, nLen AS INT

LOCAL cLine AS STRING

LOCAL nDown, nAcross, nAdTextHeight AS Double

LOCAL oPPCthis AS PaperCoords

oPPCthis := oPPC

nDown := oPPCthis:nDown

nAcross := oPPCthis:nAcross

nLen := aText:Length

nAdTextHeight := SELF:TextHeight("PLQjy")

FOR i := 1 UPTO nLen

cLine := aText[i]

SELF:TextRight(oPPCthis, cLine)

//----- move down for next line of block text -----

nDown += nAdTextHeight

oPPCthis := PaperCoords{nDown,nAcross}

NEXT

RETURN

METHOD BlockRightRestrictRHS(oPPC AS PaperCoords, aText AS STRING[], nAcrossRHS AS Double) AS VOID

LOCAL i, nLen AS INT

LOCAL cLine AS STRING

LOCAL nDown, nAcross, nAdTextHeight, nWidth AS Double

LOCAL oPPCthis AS PaperCoords

LOCAL nLine, nAdjust AS Double

oPPCthis := oPPC

nAdjust := 1.0

nDown := oPPCthis:nDown

nAcross := oPPCthis:nAcross

nWidth := nAcrossRHS-nAcross

nLen := aText:Length

nAdTextHeight := SELF:TextHeight("PLQjy")

FOR i := 1 UPTO nLen

cLine := aText[i]

nLine := PCTextLength(cLine)

IF !nLine > nWidth

oPPCthis := PaperCoords{nDown,nAcross+(nWidth-nLine)}

SELF:Text(oPPCthis, cLine)

ENDIF

//----- move down for next line of block text -----

nDown += nAdTextHeight

NEXT

RETURN

METHOD BlockRightRestrictRHS(oPPC AS PaperCoords, aText AS STRING[], nAcrossRHS AS Double, nHeightFactor AS Double) AS VOID

LOCAL i, nLen AS INT

LOCAL cLine AS STRING

LOCAL nDown, nAcross, nAdTextHeight, nWidth AS Double

LOCAL oPPCthis AS PaperCoords

LOCAL nLine, nAdjust AS Double

oPPCthis := oPPC

nAdjust := 1.0

nDown := oPPCthis:nDown

nAcross := oPPCthis:nAcross

nWidth := nAcrossRHS-nAcross

nLen := aText:Length

nAdTextHeight := SELF:TextHeight("PLQjy")

FOR i := 1 UPTO nLen

cLine := aText[i]

nLine := PCTextLength(cLine)

IF !nLine > nWidth

oPPCthis := PaperCoords{nDown,nAcross+(nWidth-nLine)}

SELF:Text(oPPCthis, cLine)

ENDIF

//----- move down for next line of block text -----

nDown += nAdTextHeight*nHeightFactor

NEXT

RETURN

METHOD BlockTextDecimal(oPGC AS PageCoords, cText AS STRING, nDec AS INT, nWide AS Double) AS VOID

LOCAL r8Len, nAcross, nLine, r4Half, r8Start AS Double

LOCAL i, iX, iY, INewX, nNewAcross AS INT

LOCAL cRight, cLeft AS STRING

LOCAL aParts AS STRING[]

IF cText:Trim():Length < 1

RETURN

endif

local result as double

if ! Double.TryParse(cText, ref result)

return

endif

IF ! (result > 0 .or. result < 0)

SELF:ColTextLeft(oPGC, cText, nWide)

RETURN

endif

/*IF ! (Convert.ToDouble(cText) > 0 .or. Convert.ToDouble(cText) < 0)

SELF:ColTextLeft(oPGC, cText, nWide)

RETURN

endif */

//=============================================================

IF SELF:DecimalAligns:Length < 4

SELF:FillDecimalAlign()

ENDIF

//------------------------------------

nAcross := oPGC:nAcross

nLine := oPGC:nLine

aParts := STRING[]{2}

cText := cText:Trim()

r8Len := SELF:DecimalAligns[nDec]

r8Start := nAcross+(nWide-r8Len)

aParts := SELF:GetDecimalParts(cText, nDec)

cRight := "."+aParts[2]:Trim()

oPGC := PageCoords{nLine, r8Start}

SELF:ColTextLeft(oPGC, cRight, nWide)

cLeft := aParts[1]:Trim()

r8Len := SELF:PCTextLength(cLeft)

r8Start -= r8Len-0.5 //----- al dente -----

oPGC := PageCoords{nLine, r8Start}

SELF:ColTextLeft(oPGC, cLeft, nWide)

RETURN

METHOD BlockTextDecimal(oPPC AS PaperCoords, cText AS STRING, nDec AS INT, nWide AS Double) AS VOID

LOCAL oPGC AS PageCoords

oPGC := SELF:ConvertPaperCoords2PageCoords(oPPC)

SELF:BlockTextDecimal(oPGC, cText, nDec, nWide)

RETURN

METHOD BlockDecimal(oPGC AS PageCoords, aText AS STRING[], nDec AS INT, nWidth AS Double) AS VOID

LOCAL i, nLen AS INT

LOCAL cLine AS STRING

LOCAL nLine, nAcross AS Double

LOCAL oPGCthis AS PageCoords

oPGCthis := oPGC

nLine := oPGCthis:nLine

nAcross := oPGCthis:nAcross

nLen := aText:Length

FOR i := 1 UPTO nLen

cLine := aText[i]

SELF:BlockTextDecimal(oPGCthis, cLine, nDec, nWidth)

nLine +=1

oPGCthis := PageCoords{nLine,nAcross}

NEXT

SELF:NextLine := (INT)Math.Round(nLine + 0.5)

RETURN

METHOD BlockDecimal(oPGC AS PageCoords, aText AS STRING[], nDec AS INT, nWidth AS Double, nHeightFactor AS Double) AS VOID

LOCAL i, nLen AS INT

LOCAL cLine AS STRING

LOCAL nLine, nAcross AS Double

LOCAL oPGCthis AS PageCoords

oPGCthis := oPGC

nLine := oPGCthis:nLine

nAcross := oPGCthis:nAcross

nLen := aText:Length

FOR i := 1 UPTO nLen

cLine := aText[i]

SELF:BlockTextDecimal(oPGCthis, cLine, nDec, nWidth)

nLine +=1*nHeightFactor

oPGCthis := PageCoords{nLine,nAcross}

NEXT

SELF:NextLine := (INT)Math.Round(nLine + 0.5)

RETURN

METHOD BlockDecimal(oPPC AS PaperCoords, aText AS STRING[], nDec AS INT, nWidth AS Double) AS VOID

LOCAL i, nLen AS INT

LOCAL cLine AS STRING

LOCAL nDown, nAcross AS Double

LOCAL oPPCthis AS PaperCoords

oPPCthis := oPPC

nDown := oPPCthis:nDown

nAcross := oPPCthis:nAcross

nLen := aText:Length

FOR i := 1 UPTO nLen

cLine := aText[i]

SELF:BlockTextDecimal(oPPCthis, cLine, nDec, nWidth)

nDown += SELF:TextHeight("AQTjpy")

oPPCthis := PaperCoords{nDown,nAcross}

NEXT

RETURN

METHOD BlockDecimal(oPPC AS PaperCoords, aText AS STRING[], nDec AS INT, nWidth AS Double, nHeightFactor AS Double) AS VOID

LOCAL i, nLen AS INT

LOCAL cLine AS STRING

LOCAL nDown, nAcross AS Double

LOCAL oPPCthis AS PaperCoords

oPPCthis := oPPC

nDown := oPPCthis:nDown

nAcross := oPPCthis:nAcross

nLen := aText:Length

FOR i := 1 UPTO nLen

cLine := aText[i]

SELF:BlockTextDecimal(oPPCthis, cLine, nDec, nWidth)

nDown += SELF:TextHeight("AQTjpy")*nHeightFactor

oPPCthis := PaperCoords{nDown,nAcross}

NEXT

RETURN

METHOD BlockCenter(oPGC AS PageCoords, aText AS STRING[]) AS VOID

RETURN

METHOD BlockCenterRestrictRHS(oPGC AS PageCoords, aText AS STRING[], nAcrossRHS AS Double) AS VOID

RETURN

METHOD BlockCenter(oPPC1 AS PaperCoords, aText AS STRING[], oPPC2 AS PaperCoords) AS VOID

LOCAL i, nLen AS INT

LOCAL cLine AS STRING

LOCAL nDown, nAcross1, nAcross2, nDiff AS Double

LOCAL oPPCthis1, oPPCthis2 AS PaperCoords

oPPCthis1 := oPPC1

oPPCthis2 := oPPC2

nDown := oPPCthis1:nDown

nAcross1 := oPPCthis1:nAcross

nAcross2 := oPPCthis2:nAcross

nDiff := SELF:TextHeight("AQTjyp")

nLen := aText:Length

FOR i := 1 UPTO nLen

cLine := aText[i]

SELF:TextCenterBetween(oPPCthis1, oPPCthis2, cLine)

nDown += nDiff

oPPCthis1 := PaperCoords{nDown,nAcross1}

oPPCthis2 := PaperCoords{nDown,nAcross2}

NEXT

RETURN

METHOD BlockCenterRestrictRHS(oPPC AS PaperCoords, aText AS STRING[], nAcrossRHS AS Double) AS VOID

RETURN

METHOD BlockCenterRestrictRHS(oPPC AS PaperCoords, aText AS STRING[], nAcrossRHS AS Double, nHeightFactor AS Double) AS VOID

RETURN

METHOD BlockUnder(oPGC AS PageCoords, aText AS STRING[]) AS VOID

LOCAL i, nLen AS INT

LOCAL cLine AS STRING

LOCAL nLine, nAcross AS Double

LOCAL oPGCthis AS PageCoords

oPGCthis := oPGC

nLine := oPGCthis:nLine

nAcross := oPGCthis:nAcross

nLen := aText:Length

FOR i := 1 UPTO nLen

cLine := aText[i]

SELF:TextUnder(oPGCthis, cLine)

nLine +=1

oPGCthis := PageCoords{nLine,nAcross}

NEXT

SELF:NextLine := (INT)Math.Round(nLine + 0.5)

RETURN

METHOD BlockUnderRestrictRHS(oPGC AS PageCoords, aText AS STRING[], nAcrossRHS AS Double) AS VOID

RETURN

METHOD BlockUnder(oPPC AS PaperCoords, aText AS STRING[]) AS VOID

LOCAL i, nLen AS INT

LOCAL cLine AS STRING

LOCAL nDown, nAcross, nDiff AS Double

LOCAL oPPCthis AS PaperCoords

oPPCthis := oPPC

nDown := oPPCthis:nDown

nAcross := oPPCthis:nAcross

nDiff := SELF:TextHeight("AQTjyp")

nLen := aText:Length

FOR i := 1 UPTO nLen

cLine := aText[i]

SELF:TextUnder(oPPCthis, cLine)

nDown += nDiff

oPPCthis := PaperCoords{nDown,nAcross}

NEXT

RETURN

METHOD BlockUnder(oPPC AS PaperCoords, aText AS STRING[], nHeightFactor AS Double) AS VOID

LOCAL i, nLen AS INT

LOCAL cLine AS STRING

LOCAL nDown, nAcross, nDiff AS Double

LOCAL oPPCthis AS PaperCoords

oPPCthis := oPPC

nDown := oPPCthis:nDown

nAcross := oPPCthis:nAcross

nDiff := SELF:TextHeight("AQTjyp")*nHeightFactor

nLen := aText:Length

FOR i := 1 UPTO nLen

cLine := aText[i]

SELF:TextUnder(oPPCthis, cLine)

nDown += nDiff

oPPCthis := PaperCoords{nDown,nAcross}

NEXT

RETURN

METHOD BlockUnderRestrictRHS(oPPC AS PaperCoords, aText AS STRING[], nAcrossRHS AS Double, nHeightFactor AS Double) AS VOID

RETURN

METHOD TextUnder(oPGC AS PageCoords, cText AS STRING) AS VOID

LOCAL oPoint AS Point

LOCAL nLine AS Double

oPoint := ConvertPageCoords2Point(oPGC)

nLine := oPGC:nLine

SELF:TextUnder(oPoint, cText)

SELF:NextLine := (INT)Math.Round(nLine + 1.5)

RETURN

METHOD TextUnder(oPPC AS PaperCoords, cText AS STRING) AS VOID

LOCAL oPoint AS Point

oPoint := ConvertPaperCoords2Point(oPPC)

SELF:TextUnder(oPoint, cText)

RETURN

METHOD TextUnder(oGC AS GraphCoords, cText AS STRING) AS VOID

LOCAL oPPC AS PaperCoords

oPPC := ConvertGraphCoords2PaperCoords(oGC)

LOCAL oPoint AS Point

oPoint := ConvertPaperCoords2Point(oPPC)

SELF:TextUnder(oPoint, cText)

RETURN

METHOD TextUnder(oPoint AS Point, cText AS STRING) AS VOID

SELF:g:DrawString(cText , SELF:Font , SELF:Brush , oPoint)

LOCAL nWidth AS Double

nWidth := SELF:PCTextLength(cText)

LOCAL cUnder AS STRING

LOCAL r4Length AS Double

cUnder := "_"

r4Length := SELF:PCTextLength(cUnder)

DO WHILE r4Length < nWidth

cUnder += "_"

r4Length := SELF:PCTextLength(cUnder)

ENDDO

SELF:g:DrawString(cUnder , SELF:Font , SELF:Brush , oPoint)

RETURN

METHOD TextCenterPage(oPGC AS PageCoords, cText AS STRING) AS VOID

LOCAL oPoint AS Point

LOCAL nLine, nAcross AS double

nLine := oPGC:nLine

nAcross := SELF:FindCenterStart(cText)

oPGC := PageCoords{nLine, nAcross}

oPoint := ConvertPageCoords2Point(oPGC)

SELF:g:DrawString(cText , SELF:Font , SELF:Brush , oPoint)

SELF:NextLine := (INT)Math.Round(nLine + 1.5)

RETURN

METHOD TextCenterPage(oPPC AS PaperCoords, cText AS STRING) AS VOID

LOCAL oPoint AS Point

LOCAL nDown, nAcross AS double

nDown := oPPC:nDown

nAcross := SELF:FindCenterStart(cText)

oPPC := PaperCoords{nDown, nAcross}

oPoint := ConvertPaperCoords2Point(oPPC)

SELF:g:DrawString(cText , SELF:Font , SELF:Brush , oPoint)

RETURN

METHOD TextCenterPage(oGC AS GraphCoords, cText AS STRING) AS VOID

LOCAL oPoint AS Point

LOCAL nUp, nAcross AS Double

nUp := oGC:nUp

nAcross := SELF:FindCenterStart(cText)

oGC := GraphCoords{nAcross, nUp}

LOCAL oPPC AS PaperCoords

oPPC := ConvertGraphCoords2PaperCoords(oGC)

oPoint := ConvertPaperCoords2Point(oPPC)

SELF:g:DrawString(cText , SELF:Font , SELF:Brush , oPoint)

RETURN

METHOD TextCenterBetween(oPPC1 AS PaperCoords, oPPC2 AS PaperCoords, cText AS STRING) AS VOID

LOCAL oPoint AS Point

LOCAL nDown, nAcross, nDown1, nAcross1, nDown2, nAcross2 AS Double

LOCAL nDiff, nDiff2, nWidth, nLenText, nStart AS double

LOCAL oPPC AS PaperCoords

nDown1 := oPPC1:nDown

nDown2 := oPPC2:nDown

IF ! nDown1 = nDown2

RETURN

ENDIF

nAcross1 := oPPC1:nAcross

nAcross2 := oPPC2:nAcross

nWidth := nAcross2 - nAcross1

IF nWidth <= 0

RETURN

ENDIF

cText := cText:Trim()

nLenText := SELF:PCTextLength(cText)

IF nLenText > nWidth

RETURN

ENDIF

//=====================================

nDiff := nWidth-nLenText

nDiff2 := nDiff/2.0

nStart := nAcross1+nDiff2

nDown := oPPC1:nDown

nAcross := nStart

oPPC := PaperCoords{nDown, nAcross}

oPoint := ConvertPaperCoords2Point(oPPC)

SELF:g:DrawString(cText , SELF:Font , SELF:Brush , oPoint)

RETURN

METHOD TextCenterBetween(oPGC1 AS PageCoords, oPGC2 AS PageCoords, cText AS STRING) AS VOID

LOCAL oPPC1, oPPC2 AS PaperCoords

oPPC1 := SELF:ConvertPageCoords2PaperCoords(oPGC1)

oPPC2 := SELF:ConvertPageCoords2PaperCoords(oPGC2)

SELF:TextCenterBetween(oPPC1, oPPC2, cText)

RETURN

METHOD TextCenterBetween(oGC1 AS GraphCoords, oGC2 AS GraphCoords, cText AS STRING) AS VOID

LOCAL oPPC1, oPPC2 AS PaperCoords

oPPC1 := SELF:ConvertGraphCoords2PaperCoords(oGC1)

oPPC2 := SELF:ConvertGraphCoords2PaperCoords(oGC2)

SELF:TextCenterBetween(oPPC1, oPPC2, cText)

RETURN

METHOD TextCenterPageUnder(oPGC AS PageCoords, cText AS STRING) AS VOID

LOCAL nLine, nAcross AS double

nLine := oPGC:nLine

nAcross := SELF:FindCenterStart(cText)

oPGC := PageCoords{nLine, nAcross}

SELF:TextUnder(oPGC, cText)

SELF:NextLine := (INT)Math.Round(nLine + 1.5)

RETURN

METHOD TextCenterPageUnder(oPPC AS PaperCoords, cText AS STRING) AS VOID

LOCAL nDown, nAcross AS double

nDown := oPPC:nDown

nAcross := SELF:FindCenterStart(cText)

oPPC := PaperCoords{nDown, nAcross}

SELF:TextUnder(oPPC, cText)

RETURN

METHOD TextCenterPageUnder(oGC AS GraphCoords, cText AS STRING) AS VOID

LOCAL nUp, nAcross AS Double

nUp := oGC:nUp

nAcross := SELF:FindCenterStart(cText)

oGC := GraphCoords{nAcross, nUp}

LOCAL oPPC AS PaperCoords

oPPC := ConvertGraphCoords2PaperCoords(oGC)

SELF:TextUnder(oPPC, cText)

RETURN

METHOD FindCenterStart(cText AS STRING) AS Double

LOCAL nResult AS Double

nResult := 50

LOCAL r8LeftBit, r8CenterBit, r8Hundred AS Double

IF cText:Length < 1

RETURN nResult

ENDIF

r8Hundred := 99.9999

r8CenterBit := SELF:PCTextLength(cText)

r8LeftBit := 0.5*(r8Hundred-r8CenterBit)

nResult := Math.Round(r8LeftBit+0.5)

RETURN nResult

METHOD DrawLine(oPoint1 AS Point, oPoint2 AS Point, nPenWidth AS Double) AS VOID

LOCAL oThisPen AS Pen

oThisPen := SELF:Pen

oThisPen:Width := (Single)nPenWidth

SELF:g:DrawLine(oThisPen, oPoint1, oPoint2)

RETURN

METHOD DrawLine(oPGC1 AS PageCoords, oPGC2 AS PageCoords, nPenWidth AS Double) AS VOID

LOCAL oPoint1, oPoint2 AS Point

oPoint1 := ConvertPageCoords2Point(oPGC1)

oPoint2 := ConvertPageCoords2Point(oPGC2)

SELF:DrawLine(oPoint1, oPoint2, nPenWidth)

RETURN

METHOD DrawLine(oPPC1 AS PaperCoords, oPPC2 AS PaperCoords, nPenWidth AS Double) AS VOID

LOCAL oPoint1, oPoint2 AS Point

oPoint1 := ConvertPaperCoords2Point(oPPC1)

oPoint2 := ConvertPaperCoords2Point(oPPC2)

SELF:DrawLine(oPoint1, oPoint2, nPenWidth)

RETURN

METHOD DrawLine(oGC1 AS GraphCoords, oGC2 AS GraphCoords, nPenWidth AS Double) AS VOID

LOCAL oPPC1, oPPC2 AS PaperCoords

oPPC1 := ConvertGraphCoords2PaperCoords(oGC1)

oPPC2 := ConvertGraphCoords2PaperCoords(oGC2)

LOCAL oPoint1, oPoint2 AS Point

oPoint1 := ConvertPaperCoords2Point(oPPC1)

oPoint2 := ConvertPaperCoords2Point(oPPC2)

SELF:DrawLine(oPoint1, oPoint2, nPenWidth)

RETURN

METHOD MemoPlace(oPGC AS PageCoords, cText AS STRING, nWide AS Double) AS VOID

LOCAL aText AS STRING[]

aText := Memo2Lines(cText, nWide)

SELF:Block(oPGC, aText)

RETURN

METHOD MemoPlace(oPPC AS PaperCoords, cText AS STRING, nWide AS Double) AS VOID

LOCAL aText AS STRING[]

aText := Memo2Lines(cText, nWide)

SELF:Block(oPPC, aText)

RETURN

METHOD MemoPlace(oGC AS GraphCoords, cText AS STRING, nWide AS Double) AS VOID

LOCAL aText AS STRING[]

LOCAL oPPC AS PaperCoords

oPPC := ConvertGraphCoords2PaperCoords(oGC)

aText := Memo2Lines(cText, nWide)

SELF:Block(oPPC, aText)

RETURN

METHOD MemoPlaceFullyJustify(oPGC AS PageCoords, cText AS STRING, nWide AS Double) AS VOID

LOCAL aText AS STRING[]

aText := Memo2LinesSameLength(cText, nWide)

SELF:Block(oPGC, aText)

RETURN

METHOD MemoPlaceFullyJustify(oPPC AS PaperCoords, cText AS STRING, nWide AS Double) AS VOID

LOCAL aText AS STRING[]

aText := Memo2LinesSameLength(cText, nWide)

SELF:Block(oPPC, aText)

RETURN

METHOD MemoPlaceFullyJustify(oGC AS GraphCoords, cText AS STRING, nWide AS Double) AS VOID

LOCAL aText AS STRING[]

LOCAL oPPC AS PaperCoords

oPPC := ConvertGraphCoords2PaperCoords(oGC)

aText := Memo2LinesSameLength(cText, nWide)

SELF:Block(oPPC, aText)

RETURN

METHOD PageUpdate() AS VOID

IF SELF:AutoPage

SELF:NumberOfPages +=1

ENDIF

RETURN

METHOD PCTextLength(cText AS STRING) AS Double

LOCAL fResult, fWidth AS Double

fWidth := SELF:TextLength(cText)

fResult := 99.9999*fWidth/SELF:PageWidth

RETURN fResult

METHOD Words(cGivenString AS STRING) AS STRING[]

LOCAL iP1, iP2, i, nLen AS INT

LOCAL cLline, cRline, cRest, cText AS STRING

LOCAL aWords, aRest AS STRING[]

LOCAL alWords AS ArrayList

alWords := ArrayList{}

cGivenString := cGivenString:Trim()

//---------------- check for length -----------------

IF cGivenString:Length < 1

aWords := NULL_OBJECT

RETURN aWords

ENDIF

//------------------------ check for no spaces ------------------------

IF ! cGivenString:Contains(" ")

aWords := STRING[]{1}

aWords[1] := cGivenString

RETURN aWords

ENDIF

iP1 := cGivenString:IndexOf(" ")

alWords:Add(cGivenString:Substring(0,iP1+1):Trim())

cRline := cGivenString:Substring(iP1+1):Trim()

cRest := ""

IF ! cRline:Contains(" ")

alWords:Add(cRline)

ELSE

aRest := SELF:Words(cRline) //--- recursive call to method ---

ENDIF

IF ! aRest = NULL_OBJECT

nLen := aRest:Length

FOR i := 1 UPTO nLen

cText := aRest[i]:Trim()

alWords:Add(cText)

NEXT

ENDIF

aWords := (STRING[])alWords:ToArray(typeof(STRING))

RETURN aWords

METHOD WordsList(cGivenString AS STRING) AS ArrayList

LOCAL iP1, iP2, i, nLen AS INT

LOCAL cLline, cRline, cRest, cText AS STRING

LOCAL aWords, aRest AS STRING[]

LOCAL alWords AS ArrayList

alWords := ArrayList{}

cGivenString := cGivenString:Trim()

//---------------- check for length -----------------

IF cGivenString:Length < 1

RETURN alWords

ENDIF

//------------------------ check for no spaces ------------------------

IF ! cGivenString:Contains(" ")

alWords:Add(cGivenString)

RETURN alWords

ENDIF

iP1 := cGivenString:IndexOf(" ")

alWords:Add(cGivenString:Substring(0,iP1+1):Trim())

cRline := cGivenString:Substring(iP1+1):Trim()

cRest := ""

IF ! cRline:Contains(" ")

alWords:Add(cRline)

ELSE

aRest := SELF:Words(cRline) //--- recursive call to method ---

ENDIF

IF ! aRest = NULL_OBJECT

nLen := aRest:Length

FOR i := 1 UPTO nLen

cText := aRest[i]:Trim()

alWords:Add(cText)

NEXT

ENDIF

RETURN alWords

METHOD Memo2Lines(cText AS STRING, nWide AS Double) AS STRING[]

LOCAL aLines, aWords AS STRING[]

LOCAL alLines, alWords AS ArrayList

LOCAL i, nLen AS INT

LOCAL qWords AS Queue

LOCAL cWord, cLine, cNewLine AS STRING

LOCAL nThisWide AS Double

alWords := SELF:WordsList(cText)

qWords := Queue{alWords}

nLen := qWords:Count

alLines := ArrayList{}

cLine := ""

DO WHILE .T.

IF qWords:Count < 1

IF cLine:Length > 0

alLines:Add(cLine)

cLine := ""

ENDIF

EXIT

ENDIF

//----------------------------

cWord := (STRING)qWords:Peek()

IF cLine:Length > 0

cNewLine := cLine + " "+ cWord

ELSE

cNewLine := cWord

ENDIF

nThisWide := SELF:PCTextLength(cNewLine)

IF nThisWide < nWide

cLine := cNewLine

qWords:Dequeue()

LOOP

ENDIF

IF nThisWide = nWide

cLine += cNewLine

qWords:Dequeue()

alLines:Add(cLine)

cLine := ""

LOOP

ENDIF

alLines:Add(cLine)

cLine := ""

ENDDO

aLines := (STRING[])alLines:ToArray(typeof(STRING))

RETURN aLines

METHOD Memo2LinesSameLength(cText AS STRING, nWide AS Double) AS STRING[]

LOCAL aLines, aWords AS STRING[]

LOCAL alLines, alWords AS ArrayList

LOCAL i, nLen AS INT

LOCAL qWords AS Queue

LOCAL cWord, cLine, cNewLine AS STRING

LOCAL nThisWide AS Double

alWords := SELF:WordsList(cText)

qWords := Queue{alWords}

nLen := qWords:Count

alLines := ArrayList{}

cLine := ""

DO WHILE .T.

IF qWords:Count < 1

IF cLine:Length > 0

alLines:Add(cLine)

cLine := ""

ENDIF

EXIT

ENDIF

//----------------------------

cWord := (STRING)qWords:Peek()

IF cLine:Length > 0

cNewLine := cLine + " "+ cWord

ELSE

cNewLine := cWord

ENDIF

nThisWide := SELF:PCTextLength(cNewLine)

IF nThisWide < nWide

cLine := cNewLine

qWords:Dequeue()

LOOP

ENDIF

IF nThisWide = nWide

cLine += cNewLine

qWords:Dequeue()

alLines:Add(Line2SpecifiedLength(cLine, nWide))

cLine := ""

LOOP

ENDIF

alLines:Add(Line2SpecifiedLength(cLine, nWide))

cLine := ""

ENDDO

aLines := (STRING[])alLines:ToArray(typeof(STRING))

RETURN aLines

METHOD Line2SpecifiedLength(cLine AS STRING, nWide AS Double) AS STRING

LOCAL cResult, cNewLine AS STRING

LOCAL aGaps AS STRING[]

LOCAL nLen, nPointer, i AS INT

LOCAL nThisWide AS Double

nThisWide := SELF:PCTextLength(cLine)

IF nThisWide >= nWide

cResult := cLine

RETURN cResult

ENDIF

IF ! cLine:Contains(" ")

cResult := cLine

RETURN cResult

ENDIF

LOCAL aWords AS STRING[]

aWords := SELF:Words(cLine)

nLen := aWords:Length

aGaps := STRING[]{nLen-1} //----- telegraph poles and gaps / spaces thing ! -----

FOR i := 1 UPTO nLen-1

aGaps[i] := " "

NEXT

nPointer := 1

DO WHILE .T.

cNewLine := SELF:ConcatLine(aWords, aGaps)

nThisWide := SELF:PCTextLength(cNewLine)

IF nThisWide = nWide

EXIT

ENDIF

IF nThisWide > nWide

/*cLine := aGaps[nPointer]

nLen := cLine:Length

aGaps[nPointer] := cLine:Substring(0,nLen-1) */

EXIT

ENDIF

nPointer +=1

IF nPointer > nLen-1

nPointer := 1

ENDIF

aGaps[nPointer] += " "

ENDDO

cResult := SELF:ConcatLine(aWords, aGaps)

RETURN cResult

METHOD ConcatLine(aWords AS STRING[], aGaps AS STRING[]) AS STRING

LOCAL cResult AS STRING

LOCAL i, nLen AS INT

cResult := ""

nLen := aWords:Length

FOR i := 1 UPTO nLen-1

cResult += aWords[i]+aGaps[i]

NEXT

cResult += aWords[nLen]

RETURN cResult

METHOD VLineSet(oPGC AS PageCoords, N AS INT, x AS Double, y2 AS Double) AS VOID

//---- oPGC (x1,y1) is top left (PAGE) coords, N number of lines, x spacing ----

//------------ y2 is bottom limit (so length is y2 - oPGC:nLine) ----

LOCAL i AS INT

LOCAL x1, y1 AS Double

LOCAL lError AS LOGIC

lError := FALSE

x1 := oPGC:nAcross

y1 := oPGC:nLine

IF y1 >= y2

lError := TRUE

ENDIF

IF N < 2

lError := TRUE

ENDIF

IF x <= 0

lError := TRUE

ENDIF

IF (N-1)*x+x1 > 100

lError := TRUE

ENDIF

IF lError

Messagebox.Show("error in VLineSet ???")

SELF:Error := TRUE

SELF:LastErrorMessage := "VLineSet not specified correctly."

RETURN

ENDIF

//------------------------------------------

LOCAL oPoint1, oPoint2 AS Point

LOCAL oPGCnew AS PageCoords

FOR i := 0 UPTO N-1

oPGCnew := PageCoords{y1, x1+i*x}

oPoint1 := ConvertPageCoords2Point(oPGCnew)

oPGCnew := PageCoords{y2, x1+i*x}

oPoint2 := ConvertPageCoords2Point(oPGCnew)

SELF:g:DrawLine(SELF:Pen, oPoint1, oPoint2)

NEXT

lError := FALSE

RETURN

METHOD VLineSet(oPPC AS PaperCoords, N AS INT, x AS Double, y2 AS Double) AS VOID

//---- oPPC (x1,y1) is top left (PAPER) coords, N number of lines, x spacing ----

//------------ y2 is bottom limit (so length is y2 - oPGC:nLine) ----

LOCAL i AS INT

LOCAL x1, y1 AS Double

LOCAL lError AS LOGIC

lError := FALSE

x1 := oPPC:nAcross

y1 := oPPC:nDown

IF y1 >= y2

lError := TRUE

ENDIF

IF N < 2

lError := TRUE

ENDIF

IF x <= 0

lError := TRUE

ENDIF

IF (N-1)*x+x1 > 100

lError := TRUE

ENDIF

IF lError

Messagebox.Show("error in VLineSet ???")

SELF:Error := TRUE

SELF:LastErrorMessage := "VLineSet not specified correctly."

RETURN

ENDIF

//------------------------------------------

LOCAL oPoint1, oPoint2 AS Point

LOCAL oPPCnew AS PaperCoords

FOR i := 0 UPTO N-1

oPpCnew := PaperCoords{y1, x1+i*x}

oPoint1 := ConvertPaperCoords2Point(oPPCnew)

oPPCnew := PaperCoords{y2, x1+i*x}

oPoint2 := ConvertPaperCoords2Point(oPPCnew)

SELF:g:DrawLine(SELF:Pen, oPoint1, oPoint2)

NEXT

lError := FALSE

RETURN

METHOD VLineSet(oGC AS GraphCoords, N AS INT, x AS Double, y2 AS Double) AS VOID

//---- oGC (x1,y1) is top left (GRAPH) coords, N number of lines, x spacing ----

//------------ y2 is bottom limit (so length is y2 - oGC:nUp) ----

LOCAL oPPC AS PaperCoords

oPPC := ConvertGraphCoords2PaperCoords(oGC)

SELF:VLineSet(oPPC, N, x, y2)

RETURN

METHOD VLineSetComplex(oPGC AS PageCoords, aX AS Double[], y2 AS Double) AS VOID

//--------- oPGC is top left (PAGE) coords, N number of lines, -----------

//--------- aX is a set of spacings in the x direction -------------

//------------ y2 is bottom limit (so length is y2 - oPGC:nLine) ----

LOCAL i, iLen, N AS INT

LOCAL x1, y1, xG, xSum AS Double

LOCAL lError AS Boolean

lError := FALSE

N := aX:Length

x1 := oPGC:nAcross

y1 := oPGC:nLine

IF y1 >= y2

lError := TRUE

ENDIF

IF N < 1

lError := TRUE

ENDIF

xSum := 0.0

FOR i := 1 UPTO N

xSum += aX[i]

NEXT

IF xSum+x1 > 100

lError := TRUE

ENDIF

IF lError

//Messagebox.Show("error in VLineSetComplex ???")

SELF:Error := TRUE

SELF:LastErrorMessage := "VLineSetComplex not specified correctly."

RETURN

ENDIF

//-----------------------

LOCAL oPoint1, oPoint2 AS Point

LOCAL oPGCnew AS PageCoords

xG := x1

oPGCnew := PageCoords{y1, xG}

oPoint1 := ConvertPageCoords2Point(oPGCnew)

oPGCnew := PageCoords{y2, xG}

oPoint2 := ConvertPageCoords2Point(oPGCnew)

SELF:g:DrawLine(SELF:Pen, oPoint1, oPoint2)

FOR i := 1 UPTO N

xG += aX[i]

oPGCnew := PageCoords{y1, xG}

oPoint1 := ConvertPageCoords2Point(oPGCnew)

oPGCnew := PageCoords{y2, xG}

oPoint2 := ConvertPageCoords2Point(oPGCnew)

SELF:g:DrawLine(SELF:Pen, oPoint1, oPoint2)

NEXT

lError := FALSE

RETURN

METHOD HLineSet(oPGC AS PageCoords, N AS INT, y AS Double, x2 AS Double) AS VOID

//----- oPGC (x1,y1) is top left (PAGE) coords, N number of lines, y spacing -----

//------------ x2 is right limit (so length is x2 - oPGC:nAcross) -----

LOCAL i AS INT

LOCAL x1, y1 AS Double

LOCAL lError AS LOGIC

lError := FALSE

x1 := oPGC:nAcross

y1 := oPGC:nLine

IF x1 >= x2

lError := TRUE

ENDIF

IF N < 2

lError := TRUE

ENDIF

IF y <= 0

lError := TRUE

ENDIF

IF (N-1)*y+y1 > 100

lError := TRUE

ENDIF

IF lError

//Messagebox.Show("error in HLineSet ???")

SELF:Error := TRUE

SELF:LastErrorMessage := "HLineSet not specified correctly."

RETURN

ENDIF

//-----------------------

LOCAL oPoint1, oPoint2 AS Point

LOCAL oPGCnew AS PageCoords

FOR i := 0 UPTO N-1

oPGCnew := PageCoords{y1+i*y, x1}

oPoint1 := ConvertPageCoords2Point(oPGCnew)

oPGCnew := PageCoords{y1+i*y, x2}

oPoint2 := ConvertPageCoords2Point(oPGCnew)

SELF:g:DrawLine(SELF:Pen, oPoint1, oPoint2)

NEXT

lError := FALSE

RETURN

METHOD HLineSet(oPPC AS PaperCoords, N AS INT, y AS Double, x2 AS Double) AS VOID

//----- oPPC (x1,y1) is top left (PAPER) coords, N number of lines, y spacing -----

//------------ x2 is right limit (so length is x2 - oPPC:nAcross) -----

LOCAL i AS INT

LOCAL x1, y1 AS Double

LOCAL lError AS LOGIC

lError := FALSE

x1 := oPPC:nAcross

y1 := oPPC:nDown

IF x1 >= x2

lError := TRUE

ENDIF

IF N < 2

lError := TRUE

ENDIF

IF y <= 0

lError := TRUE

ENDIF

IF (N-1)*y+y1 > 100

lError := TRUE

ENDIF

IF lError

//Messagebox.Show("error in HLineSet ???")

SELF:Error := TRUE

SELF:LastErrorMessage := "HLineSet not specified correctly."

RETURN

ENDIF

//-----------------------

LOCAL oPoint1, oPoint2 AS Point

LOCAL oPPCnew AS PaperCoords

FOR i := 0 UPTO N-1

oPPCnew := PaperCoords{y1+i*y, x1}

oPoint1 := ConvertPaperCoords2Point(oPPCnew)

oPPCnew := PaperCoords{y1+i*y, x2}

oPoint2 := ConvertPaperCoords2Point(oPPCnew)

SELF:g:DrawLine(SELF:Pen, oPoint1, oPoint2)

NEXT

lError := FALSE

RETURN

METHOD HLineSet(oGC AS GraphCoords, N AS INT, y AS Double, x2 AS Double) AS VOID

//----- oGC (x1,y1) is top left (GRAPH) coords, N number of lines, y spacing -----

//------------ x2 is right limit (so length is x2 - oGC:nAcross) -----

LOCAL oPPC AS PaperCoords

oPPC := ConvertGraphCoords2PaperCoords(oGC)

SELF:HLineSet(oPPC, N, y, x2)

RETURN

METHOD HLineSetComplex(oPGC AS PageCoords, aY AS Double[], x2 AS Double) AS VOID

//---- oPGC is top left (PAGE) coords, aY the array of spaces ------

//------------ x2 is right limit (so length is x2 - oPGC:nAcross) ----

LOCAL i, N AS INT

LOCAL x1, y1, ySum, yG AS Double

LOCAL lError AS Boolean

lError := FALSE

x1 := oPGC:nAcross

y1 := oPGC:nLine

N := aY:Length

IF x1 >= x2

lError := TRUE

ENDIF

IF N < 1

lError := TRUE

ENDIF

ySum := 0.0

FOR i := 1 UPTO N

ySum += aY[i]

NEXT

IF ySum+y1 > SELF:LinesPerPage

lError := TRUE

ENDIF

IF lError

//Messagebox.Show("error in HLineSetComplex ???")

SELF:Error := TRUE

SELF:LastErrorMessage := "HLineSetComplex not specified correctly."

RETURN

ENDIF

//-----------------------

LOCAL oPoint1, oPoint2 AS Point

LOCAL oPGCnew AS PageCoords

yG := y1

oPGCnew := PageCoords{yG, x1}

oPoint1 := ConvertPageCoords2Point(oPGCnew)

oPGCnew := PageCoords{yG, x2}

oPoint2 := ConvertPageCoords2Point(oPGCnew)

SELF:g:DrawLine(SELF:Pen, oPoint1, oPoint2)

FOR i := 1 UPTO N

yG += aY[i]

oPGCnew := PageCoords{yG, x1}

oPoint1 := ConvertPageCoords2Point(oPGCnew)

oPGCnew := PageCoords{yG, x2}

oPoint2 := ConvertPageCoords2Point(oPGCnew)

SELF:g:DrawLine(SELF:Pen, oPoint1, oPoint2)

NEXT

lError := FALSE

RETURN

METHOD GridRegularNbyM(oPGC1 AS PageCoords, oPGC2 AS PageCoords, N AS INT, M AS INT) AS VOID

LOCAL x1, x2, y1, y2 AS Double

LOCAL xDiff, yDiff, xInc, yInc AS Double

x1 := oPGC1:nAcross

y1 := oPGC1:nLine

x2 := oPGC2:nAcross

y2 := oPGC2:nLine

xDiff := x2-x1

yDiff := y2-y1

xInc := xDiff/M

yInc := yDiff/N

SELF:VLineSet(oPGC1, M+1, xInc, y2)

SELF:HLineSet(oPGC1, N+1, yInc, x2)

RETURN

METHOD GridRegularNbyMX(oPPC1 AS PaperCoords, oPPC2 AS PaperCoords, N AS INT, M AS INT, Q AS Boolean) AS VOID

//----- top left to bottom right -----

LOCAL x1, x2, y1, y2 AS Double

LOCAL xDiff, yDiff, xInc, yInc AS Double

x1 := oPPC1:nAcross

y1 := oPPC1:nDown

x2 := oPPC2:nAcross

y2 := oPPC2:nDown

xDiff := x2-x1

yDiff := y2-y1

xInc := xDiff/M

yInc := yDiff/N

SELF:VLineSet(oPPC1, M+1, xInc, y2)

SELF:HLineSet(oPPC1, N+1, yInc, x2)

RETURN

METHOD GridRegularNbyM(oPPC1 AS PaperCoords, oPPC2 AS PaperCoords, N AS INT, M AS INT) AS VOID

LOCAL oPGC1, oPGC2 AS PageCoords

oPGC1 := ConvertPaperCoords2PageCoords(oPPC1)

oPGC2 := ConvertPaperCoords2PageCoords(oPPC2)

SELF:GridRegularNbyM(oPGC1, oPGC2, N,M)

RETURN

METHOD GridRegularNbyM(oGC1 AS GraphCoords, oGC2 AS GraphCoords, N AS INT, M AS INT) AS VOID

//----- top left to bottom right -----

LOCAL oPPC1, oPPC2 AS PaperCoords

oPPC1 := ConvertGraphCoords2PaperCoords(oGC1)

oPPC2 := ConvertGraphCoords2PaperCoords(oGC2)

SELF:GridRegularNbyM(oPPC1, oPPC2, N, M)

RETURN

METHOD GridComplex(oPGC AS PageCoords, aX AS Double[], aY AS Double[]) AS VOID

//---- top left - rectangular grid - (PAGE coords) ------

//---- bottom right determined by spacings total -----

//---- aX is a set of spacings for vertical lines -----

//---- aY is a set of spacing for horizontal lines -----

//---- number of lines determined by arrays ----

LOCAL i, N, M AS INT

LOCAL x1, y1 AS Double

LOCAL x2, y2 AS Double

x1 := oPGC:nAcross

y1 := oPGC:nLine

N := aY:Length

M := aX:Length

x2 := x1

FOR i := 1 UPTO (M)

x2 += aX[i]

NEXT

y2 := y1

FOR i := 1 UPTO (N)

y2 += aY[i]

NEXT

SELF:VLineSetComplex(oPGC, aX, y2)

SELF:HLineSetComplex(oPGC, aY, x2)

//------ what of closing last line - to do a neat job ? -------

//------ could miss last from iterator and add calculated ! ---

RETURN

METHOD GridComplexX(oPPC AS PaperCoords, aX AS Double[], aY AS Double[], Q AS Boolean) AS VOID

//---- top left - rectangular grid - (PAGE coords) ------

//---- bottom right determined by spacings total -----

//---- aX is a set of spacings for vertical lines -----

//---- aY is a set of spacing for horizontal lines -----

//---- number of lines determined by arrays ----

/*LOCAL i, N, M AS INT

LOCAL x1, y1 AS Double

LOCAL x2, y2 AS Double

x1 := oPGC:nAcross

y1 := oPGC:nLine

N := aY:Length

M := aX:Length

x2 := x1

FOR i := 1 UPTO (M)

x2 += aX[i]

NEXT

y2 := y1

FOR i := 1 UPTO (N)

y2 += aY[i]

NEXT

SELF:VLineSetComplex(oPPC, aX, y2)

SELF:HLineSetComplex(oPPC, aY, x2)

*/

//------ what of closing last line - to do a neat job ? -------

//------ could miss last from iterator and add calculated ! ---

RETURN

METHOD GridComplex(oPPC AS PaperCoords, aX AS Double[], aY AS Double[]) AS VOID

LOCAL oPGC AS PageCoords

oPGC := SELF:ConvertPaperCoords2PageCoords(oPPC)

SELF:GridComplex(oPGC, aX, aY)

RETURN

METHOD BitmapPlace(oPGC AS PageCoords, cBitMapPath AS STRING) AS VOID

LOCAL oMyImage AS Bitmap

LOCAL oPoint AS Point

LOCAL oMyFile AS FileInfo

oMyFile := FileInfo{cBitMapPath}

IF ! oMyFile:Exists

oMyFile := NULL_OBJECT

RETURN

ENDIF

//-----------------------------------------------

oMyImage := Bitmap{cBitMapPath}

oPoint := ConvertPageCoords2Point(oPGC)

SELF:g:DrawImage(oMyImage, oPoint)

oMyImage:Dispose()

RETURN

METHOD BitmapPlace(oPPC AS PaperCoords, cBitMapPath AS STRING) AS VOID

LOCAL oMyImage AS Bitmap

LOCAL oPoint AS Point

LOCAL oMyFile AS FileInfo

oMyFile := FileInfo{cBitMapPath}

IF ! oMyFile:Exists

oMyFile := NULL_OBJECT

RETURN

ENDIF

//-----------------------------------------------

oMyImage := Bitmap{cBitMapPath}

oPoint := ConvertPaperCoords2Point(oPPC)

SELF:g:DrawImage(oMyImage, oPoint)

oMyImage:Dispose()

RETURN

METHOD BitmapPlace(oGC AS GraphCoords, cBitMapPath AS STRING) AS VOID

LOCAL oMyImage AS Bitmap

LOCAL oPoint AS Point

LOCAL oMyFile AS FileInfo

oMyFile := FileInfo{cBitMapPath}

IF ! oMyFile:Exists

oMyFile := NULL_OBJECT

RETURN

ENDIF

//-----------------------------------------------

oMyImage := Bitmap{cBitMapPath}

LOCAL oPPC AS PaperCoords

oPPC := ConvertGraphCoords2PaperCoords(oGC)

oPoint := ConvertPaperCoords2Point(oPPC)

SELF:g:DrawImage(oMyImage, oPoint)

oMyImage:Dispose()

RETURN

METHOD BitmapPlaceScaled(oPGC AS PageCoords, cBitMapPath AS STRING, nFactor AS Double) AS VOID

LOCAL oMyImage, oMyImage2 AS Bitmap

LOCAL oPoint AS Point

LOCAL oMyFile AS FileInfo

LOCAL nWidth, nHeight AS INT

LOCAL oSize AS Size

oMyFile := FileInfo{cBitMapPath}

IF ! oMyFile:Exists

oMyFile := NULL_OBJECT

RETURN

ENDIF

//-----------------------------------------------

oMyImage := Bitmap{cBitMapPath}

oPoint := ConvertPageCoords2Point(oPGC)

oSize := oMyImage:Size

nWidth := oSize:Width

nHeight := oSize:Height

oMyImage2 := Bitmap{oMyImage, (INT)Math.Round(nFactor*nWidth), (INT)Math.Round(nFactor*nHeight)}

SELF:g:DrawImage(oMyImage2, oPoint)

oMyImage:Dispose()

oMyImage2:Dispose()

RETURN

METHOD BitmapPlaceScaled(oPPC AS PaperCoords, cBitMapPath AS STRING, nFactor AS Double) AS VOID

LOCAL oMyImage, oMyImage2 AS Bitmap

LOCAL oPoint AS Point

LOCAL oMyFile AS FileInfo

LOCAL nWidth, nHeight AS INT

LOCAL oSize AS Size

oMyFile := FileInfo{cBitMapPath}

IF ! oMyFile:Exists

oMyFile := NULL_OBJECT

RETURN

ENDIF

//-----------------------------------------------

oMyImage := Bitmap{cBitMapPath}

oPoint := ConvertPaperCoords2Point(oPPC)

oSize := oMyImage:Size

nWidth := oSize:Width

nHeight := oSize:Height

oMyImage2 := Bitmap{oMyImage, (INT)Math.Round(nFactor*nWidth), (INT)Math.Round(nFactor*nHeight)}

SELF:g:DrawImage(oMyImage2, oPoint)

oMyImage:Dispose()

oMyImage2:Dispose()

RETURN

METHOD BitmapPlaceScaled(oGC AS GraphCoords, cBitMapPath AS STRING, nFactor AS Double) AS VOID

LOCAL oMyImage, oMyImage2 AS Bitmap

LOCAL oPoint AS Point

LOCAL oMyFile AS FileInfo

LOCAL nWidth, nHeight AS INT

LOCAL oSize AS Size

oMyFile := FileInfo{cBitMapPath}

IF ! oMyFile:Exists

oMyFile := NULL_OBJECT

RETURN

ENDIF

//-----------------------------------------------

oMyImage := Bitmap{cBitMapPath}

LOCAL oPPC AS PaperCoords

oPPC := ConvertGraphCoords2PaperCoords(oGC)

oPoint := ConvertPaperCoords2Point(oPPC)

oSize := oMyImage:Size

nWidth := oSize:Width

nHeight := oSize:Height

oMyImage2 := Bitmap{oMyImage, (INT)Math.Round(nFactor*nWidth), (INT)Math.Round(nFactor*nHeight)}

SELF:g:DrawImage(oMyImage2, oPoint)

oMyImage:Dispose()

oMyImage2:Dispose()

RETURN

METHOD BitmapPlaceAtBottomLeft(oPGC AS PageCoords, cBitMapPath AS STRING) AS VOID

LOCAL oMyImage AS Image

LOCAL oPoint AS Point

LOCAL oMyFile AS FileInfo

LOCAL oSize AS Size

LOCAL nWidth, nHeight AS INT

LOCAL X,Y AS INT

oMyFile := FileInfo{cBitMapPath}

IF ! oMyFile:Exists

oMyFile := NULL_OBJECT

RETURN

ENDIF

//-----------------------------------------------

oMyImage := Bitmap{cBitMapPath}

oSize := oMyImage:Size

nWidth := oSize:Width

nHeight := oSize:Height

oPoint := ConvertPageCoords2Point(oPGC)

X := oPoint:X

Y := oPoint:Y

oPoint := Point{X, Y-nHeight}

SELF:g:DrawImage(oMyImage,oPoint)

oMyImage:Dispose()

RETURN

METHOD BitmapPlaceTransparent(oPGC AS PageCoords, cBitMapPath AS STRING) AS VOID

LOCAL oBitmap AS Bitmap

LOCAL oPoint AS Point

LOCAL oMyFile AS FileInfo

oMyFile := FileInfo{cBitMapPath}

IF ! oMyFile:Exists

oMyFile := NULL_OBJECT

RETURN

ENDIF

//-----------------------------------------------

oBitmap := Bitmap{cBitMapPath}

oBitmap:MakeTransparent(Color.White)

oPoint := ConvertPageCoords2Point(oPGC)

SELF:g:DrawImage(oBitmap, oPoint)

oBitmap:Dispose()

RETURN

METHOD BitmapPlaceTransparent(oPGC AS PageCoords, cBitMapPath AS STRING, oColor AS Color) AS VOID

LOCAL oBitmap AS Bitmap

LOCAL oPoint AS Point

LOCAL oMyFile AS FileInfo

oMyFile := FileInfo{cBitMapPath}

IF ! oMyFile:Exists

oMyFile := NULL_OBJECT

RETURN

ENDIF

//-----------------------------------------------

oBitmap := Bitmap{cBitMapPath}

oBitmap:MakeTransparent(oColor)

oPoint := ConvertPageCoords2Point(oPGC)

SELF:g:DrawImage(oBitmap, oPoint)

oBitmap:Dispose()

RETURN

METHOD GetLeftDecimal(cText AS STRING) AS STRING

LOCAL cLeft AS STRING

LOCAL nIndex AS INT

cLeft := ""

nIndex := cText:IndexOf(".")

IF nIndex > 1

cLeft := cText:Substring(0,nIndex):Trim()

ENDIF

//Messagebox.Show(cLeft:ToString())

RETURN cLeft

METHOD GetDecimalParts(cText AS STRING, nDec AS INT) AS STRING[]

LOCAL cLeft, cRight AS STRING

LOCAL nIndex AS INT

LOCAL aParts AS STRING[]

aParts := <STRING>{"",""}

cLeft := ""

cRight := "00000"

IF !cText:Contains(".")

cLeft := cText:Trim()

ELSE

nIndex := cText:IndexOf(".")

cLeft := cText:Substring(0,nIndex):Trim()

cRight := cText:Substring(nIndex+1):Trim()+"00000"

//cRight := cRight:Substring(0,nDec)

ENDIF

cRight := cRight:Substring(0,nDec)

//Messagebox.Show(cLeft:ToString())

//Messagebox.Show(cRight:ToString())

aParts := <STRING>{cLeft, cRight}

RETURN aParts

METHOD FillDecimalAlign() AS VOID

LOCAL i AS INT

LOCAL cRight , cText AS STRING

LOCAL r8Len AS Double

SELF:DecimalAligns := Double[]{4}

cRight := ""

FOR i := 1 UPTO 4

cRight += "0"

cText := "."+cRight

r8Len := SELF:PCTextLength(cText)

SELF:DecimalAligns[i] := r8Len

NEXT

RETURN

METHOD ColTextLeft(oPGC AS PageCoords, cText AS STRING, nWide AS Double) AS VOID

LOCAL nLine, nAcross AS Double

LOCAL oPoint AS Point

LOCAL nY, nX AS Double

LOCAL iY, iX AS INT

LOCAL cLine AS STRING

IF cText:Trim():Length < 1

RETURN

ENDIF

//----- chop line if too long ! -----

cLine := SELF:TextRestrictRHS(cText, nWide)

nLine := oPGC:nLine

nAcross := oPGC:nAcross

IF nLine < 1 .or. nLine > SELF:LinesPerPage

//--------- warn box ---------

nLine := 1

ENDIF

IF nAcross < 0 .or. nAcross > 100

//---------- warn box ---------

nAcross := 0.0

ENDIF

oPoint := ConvertPageCoords2Point(oPGC)

SELF:g:DrawString(cLine , SELF:Font , SELF:Brush , oPoint)

RETURN

METHOD ColTextCenter(oPGC AS PageCoords, cText AS STRING, nWide AS Double)AS VOID

LOCAL r8Len, nAcross, nLine, r8Half, r8Start, r8Remain AS Double

cText := cText:Trim()

r8Len := SELF:PCTextLength(cText)

nAcross := oPGC:nAcross

nLine := oPGC:nLine

r8Remain := nWide - r8Len

IF r8Remain <= 0

SELF:ColTextLeft(oPGC, cText, nWide)

RETURN

ENDIF

//-----------------------------------------

r8Half := r8Remain*0.5

r8Start := nAcross+r8Half

oPGC := PageCoords{nLine, r8Start}

SELF:ColTextLeft(oPGC, cText, nWide)

RETURN

METHOD ColTextRight(oPGC AS PageCoords, cText AS STRING, nWide AS Double) AS VOID

LOCAL r8Len, nAcross, nLine, r8Start AS Double

LOCAL cLine AS STRING

nAcross := oPGC:nAcross

nLine := oPGC:nLine

cText := cText:Trim()

r8Len := SELF:PCTextLength(cText)

IF r8Len > nWide

//----- chop line if too long ! -----

cLine := SELF:Line2SpecifiedLength(cText, nWide)

r8Len := SELF:PCTextLength(cLine) //----- ought to be nWide ! -----

ELSE

cLine := cText

ENDIF

r8Start := nAcross + nWide - r8Len

oPGC := PageCoords{nLine, r8Start}

SELF:ColTextLeft(oPGC, cLine, nWide)

RETURN

METHOD ColTextDecimal(oPGC AS PageCoords, cText AS STRING, nDec AS INT, nWide AS Double) AS VOID

LOCAL r8Len, nAcross, nLine, r4Half, r8Start AS Double

LOCAL i, iX, iY, INewX, nNewAcross AS INT

LOCAL cRight, cLeft AS STRING

LOCAL aParts AS STRING[]

IF SELF:DecimalAligns:Length < 4

SELF:FillDecimalAlign()

ENDIF

//------------------------------------

nAcross := oPGC:nAcross

nLine := oPGC:nLine

aParts := STRING[]{2}

cText := cText:Trim()

r8Len := SELF:DecimalAligns[nDec]

r8Start := nAcross+(nWide-r8Len)

aParts := SELF:GetDecimalParts(cText, nDec)

cRight := "."+aParts[2]:Trim()

oPGC := PageCoords{nLine, r8Start}

SELF:ColTextLeft(oPGC, cRight, nWide)

cLeft := aParts[1]:Trim()

r8Len := SELF:PCTextLength(cLeft)

r8Start -= r8Len-0.5 //----- al dente -----

//r8Start -= r8Len-0.7 //----- al dente -----

//r8Start -= r8Len-1.0 //----- al dente -----

oPGC := PageCoords{nLine, r8Start}

SELF:ColTextLeft(oPGC, cLeft, nWide)

RETURN

END CLASS

CLASS CanvasCoords

EXPORT X, Y AS INT

EXPORT oPoint AS Point

CONSTRUCTOR(nX AS INT, nY AS INT)

SUPER()

SELF:X := nX

SELF:Y := nY

SELF:oPoint := Point{X,Y}

RETURN

END CLASS

CLASS PageCoords

EXPORT nLine, nAcross AS Double

EXPORT LinesPerPage AS INT

CONSTRUCTOR(nY AS Double, nX AS Double)

SUPER()

SELF:LinesPerPage := 60

SELF:nLine := nY

SELF:nAcross := nX

RETURN

CONSTRUCTOR(nLPP AS INT, nY AS Double, nX AS Double)

SUPER()

SELF:LinesPerPage := nLPP

SELF:nLine := nY

SELF:nAcross := nX

RETURN

public method MoveStartLeft(nMR as double) as void

if (nMR >= 0 && nMR < 90 )

SELF:nAcross := nMR

endif

return

public method MoveRight(nMR as double) as void

if nAcross + nMR <= 98 //--- guesstimate!

SELF:nAcross += nMR

endif

return

public method MoveDown(nMD as int) as void

if LinesPerPage >= nLine+nMD

SELF:nLine += nMD

endif

return

public method MoveDown() as void

if nLine+1 <= LinesPerPage

SELF:nLine += 1

endif

return

END CLASS

CLASS PaperCoords

EXPORT nDown, nAcross AS Double

CONSTRUCTOR(nY AS Double, nX AS Double)

SUPER()

SELF:nDown := nY

SELF:nAcross := nX

RETURN

END CLASS

CLASS GraphCoords

EXPORT nUp, nAcross AS Double

CONSTRUCTOR(nX AS Double, nY AS Double)

SUPER()

SELF:nUp := nY

SELF:nAcross := nX

RETURN

END CLASS

//===========================================

END NAMESPACE // ClassLibraryBESTdocument

=================================================================================

PHH - 23rd. March 2018.


some Benefits of .NET over the VO printing facilities ...

I had a version of a Printing Class in Visual Objects, it was used on a daily basis with my

customer / client networks systems to print millions of letters and documents.

Two benefits spring to mind after all the ten years since I moved from VO into Vulcan, and the

world of .NET.

It was suddenly easy to find all the available printers at a PC platform, and then use these printers.

The availability of 'Overloading' made the naming of methods much easier and nicer.

From a design point of view I did not need to come up with so many slightly different method names.

Instead of having the information in the method name the differences were placed within the method signature.

Here are a few examples of useful overloading - checkout the methods to place underlined text on the graphics

surface of the 'PrintDocument' / 'BESTdocument' :-

==============================================================================================

Since printing is easier (by far) if we have more than one coordinate system to use to specify a place,

or position on the graphics surface (albeit an 'imaginary' surface), it is very handy to have overloads which

are designed to be used with the differing coordinate parameters.

Here is another simple example :-

==============================================================================================

And yet another - the 'Decimal' related methods are VERY useful as numeric output which

does not have the decimal points line up vertically, is very annoying and distracting indeed.

Check these out :-

=====================================================================================================================================================================

The members within the 'BESTdocument' ('BD') class tend to be designed in a 'layered' way.

By this I mean that more primitive methods are used to build more complex methods and so on.

Most of the very useful methods are quite a few layers on top of the 'primitive' methods provided

in the .NET 'PrintDocument' class.

Lets now look at the four later additions to the Class, these below are used throughout the Report Printing

sections which are to follow - check these out :-

=====================================================================================================================================

Now then, to show you what I mean about 'Layers' and multiple layers of methods, using

other methods created in the BD class, we can get a good idea of what I mean by checking out

the 'ColTextDecimal' method which takes Page Coordinates - the text will be similar to " 13.4567 " :-

[ .. try to count the different methods which are in lower levels of my BD methods .. ]

================================================================================================================================

Four methods and a supporting class.

DecimalAligns

GetDecimalParts

PCTextLength

ColTextLeft

PageCoords{}

These are all part of BD and not the underlying class.

================================================================================================

Look at the above image and view the Balance column. The three entries red, black, and empty are

all done using this method - the code is further below.

So to finish this sub-section, lets see how one field (Balance) of a single row in a report is printed to the

graphics surface.

Lines 522, 526, and 529 are the column methods used :-

===========================================================================================================

The benefit to using the 'Col' methods is that they allow a visual 'chop' of the data on screen without any need to

change at all the underlying data.

This is where the 'width' comes in.

Now the good news - we can ignore the operation of many of the underlying 'layers' and just get on using those

at a high, and obviously useful level.

I have personally forgotten about many I painstaking created - but I can make a nice multi-page report ;-0)

Time to move on .............

PHH - 23rd. March 2018.


Navigation: 'Printing and Printers', and Report generation too ... >

Return to chapter overview

Add to this library - make it your own

PHH - April 12th. 2017


Making Reports - prior to printing

The BIG mistake many of us developer guys make is to try and do too much at once while

printing document or reports.

We do not take enough care and effort (time if you will) to prepare the data and graphical elements

before we the assemble then on the 'graphics' surface ready for printing.

If we make a good effort to prepare our data, organise it, and get it into the correct 'format', then the

process of printing is made much easier.

The case of printing multi-page, columnated reports, is probably the most important for us to well organise

our report data before we try to lay it out on the graphical surface for .NET to print it (page by page) to the

target printer.

Remember that .NET allows us to only assemble one page at a time upon the graphics surface often called 'e'.

If we pre-process the data to get it into suitable format - then printing can be a 'fun' thing. Or at least an

interesting exercise.

If however, we try to format and organise the data while trying to lay it out graphically, then we can have a very

frustrating and negative experience.

My feelings are that this is a common mistake by many developers. I have been there myself, before I saw the light!

I will discuss this topic a little further in the sub-section on Multiple Pages.

PHH - April 12th. 2017. & March 23rd. 2018.


Navigation: 'Printing and Printers', and Report generation too ... > Making Reports - prior to printing >

Return to chapter overview

Single page documents / reports

PHH - 23rd. March 2018.


Multiple page reports

================================================================================================================================

PHH - 23rd. March 2018.


Navigation: 'Printing and Printers', and Report generation too ... >

Return to chapter overview

'DataTable' - a useful Class for reporting

PHH - 30th. December 2017.


Navigation: 'Printing and Printers', and Report generation too ... >

Return to chapter overview

List<T> - an even better Class for reporting

PHH - 23rd. March 2018.


Filtering and Sorting our generic data ...

========================================================

===================================================================================================================================

============================================================================================================================

=================================================================================================================================

PHH - 23rd. March 2018.


Writing 'wrappers' around List<T> ...

====================================================================================================================

==============================================================================================

====================================================================================================================

========================================================================================

PHH - 23rd. March 2018.


Navigation: 'Printing and Printers', and Report generation too ... > List<T> - an even better Class for reporting >

Return to chapter overview

Calculated fields - adding to rows ...

PHH - 23rd. March 2018.


Navigation: 'Printing and Printers', and Report generation too ... > List<T> - an even better Class for reporting >

Return to chapter overview

Summing Rows and Columns ...

PHH - 23rd. March 2018.


using DBFs as SQL tables ...

https://stackoverflow.com/questions/16819811/how-to-convert-visual-foxpro-database-into-sql-server-database

===========================================================================================================================

========================================================================================================

============================================================================================

==================================================================================================================

=========================================================================================================

Lets have a look at doing this on a Windows 10 platform - version of Win10 is Build 16257.

Notice we have an extra line of choice - shown at line position one :-

=====================================================================================================

So here I have made a new Data Source based on dBase 3 - I think this is OK for VO DBF format. I need to

check this with Robert.

We can run with this for the moment, and change it later if we have selected anything unsuitable.

Here below is the new Data Source after it is entered in the list of sources :-

=====================================================================================================

I now need someone else to try this for me as I have no DBF files available to me.

Watch this space ..............

PHH - 2nd. January 2018.


Navigation: 'Printing and Printers', and Report generation too ... >

Return to chapter overview

using DBFs to create 'in-Memory' Collections ...

PHH - 23rd. March 2018.


Navigation: 'Printing and Printers', and Report generation too ... >

Return to chapter overview

XIDE use and creating Reports for Printing ...

PHH - 23rd. March 2018.


Navigation: 'Printing and Printers', and Report generation too ... >

Return to chapter overview

Printing the pages - using the graphics surface

PHH - 23rd. March 2018.


Page Printing event ...

=======================================================================================================

===================================================================================================================================

PHH - 23rd. March 2018.


Adding and removing the Delegate(s) ...

========================================================================================================

PHH - 23rd. March 2018.


Navigation: 'Printing and Printers', and Report generation too ... > Printing the pages - using the graphics surface >

Return to chapter overview

The 'action method' - delegate for page printing ...

PHH - 23rd. March 2018.


Navigation: 'Printing and Printers', and Report generation too ... > Printing the pages - using the graphics surface >

Return to chapter overview

Structuring the Delegate ...

PHH - 23rd. March 2018.


Navigation: 'Printing and Printers', and Report generation too ... > Printing the pages - using the graphics surface >

Return to chapter overview

Signalling another Page, or not !

PHH - 23rd. March 2018.


Navigation: 'Printing and Printers', and Report generation too ... > Printing the pages - using the graphics surface >

Return to chapter overview

Fully testing your Delegate, action method ...

PHH - 23rd. March 2018.


"Examine printers" - software app (tool) ...

The app named "Examine Printers Tool" was about the first real app that I created with .NET.

Even now I am amazed at what I did (achieved) knowing as little as I did back then about .NET and the Framework.

Needless to say that Chris Pyrgas encouraged me ;-0)

Lets look at what the app looks like on screen when running :-

[ .. there is now an X# version which is what is running to show the image - and YES it is a WinForms app ! .. ]

The reason that I managed to write this so soon in my .NET coding career is simply because .NET makes

all the necessary software tools easily available and accessible.

The related Classes have been well designed and thought out.

I used this app to good effect on site with one of my main customers. This is the customer where we developed

all the VO printing class built on the work in Alwyn's book.

We had an on-going problem on the customer's network where some software being run reset the printer driver

settings to a paper size of 'Legal'. This caused the printers to hang, since they were only loaded up with A4 paper,

which is slightly smaller in size.

Move on the above later, first lets view the simple structure of the solution :-

==========================================================

Now then, while we are at the task of explaining why the app came about, lets see what the

'Explain' button has to hide / expose :-

===================================================================

Basically, the application is to allow the IT manager, at each PC on the network in turn, to

view the available printers and see their stored settings.

From the same app he could change settings as well as sending test prints to the actual

printers. Some in colour and some in black/white.

For the appropriate printers we could also test Duplex as well.

PHH - 23rd. & 29th. March 2018.


working code ...

======================================[Start.prg]=================================================

//

// ExaminePrintersTool.prg

//

using System

using System.Windows.Forms

/// <summary>

/// The main entry point for the application.

/// </summary>

[STAThread] ;

FUNCTION Start() AS INT

  LOCAL exitCode AS INT

  exitCode := 13 //-- just to make work

  Application.EnableVisualStyles()

  Application.SetCompatibleTextRenderingDefault( false )

  Application.Run( fmExaminePrintersTool{} )

  RETURN exitCode

========================================[MyPartExaminePrintersToolVS.prg]===============================================

// MyPartExaminePrintersToolVS.prg

//-----

//----- originally written in Spring 2007 when I knew little of Vulcan -----

//----- and even less of .NET coding -----

//----- some original code has been brought up to date and the XML file -----

//----- dump used in plcace of an earlier text file CFG approach (code left in place) -----

//----- other code has just been left as it is becuase of time constraints ------

//-----

USING System.Windows.Forms

USING System.Drawing

USING System.Drawing.Printing

USING System.Text

USING System.IO

USING Microsoft.VisualBasic

partial CLASS fmExaminePrintersTool INHERIT System.Windows.Forms.Form

PROTECT cSelectedPrinter AS STRING

PROTECT DPS AS PageSettings

PROTECT oSourceCollection AS PrinterSettings.PaperSourceCollection

PROTECT oSizeCollection AS PrinterSettings.PaperSizeCollection

PROTECT oResolutionCollection AS PrinterSettings.PrinterResolutionCollection

PROTECT cPrinterName, cPaperSize, cPaperSource, cPrinterResolution AS STRING

PROTECT bLandscape, bColor, bDuplex AS LOGIC

//PROTECT nPaperSize, nPaperSource, nResolution AS DWORD

PROTECT oSB AS StringBuilder

PROTECT aDS AS STRING[]

PROTECT nPaperSize, nPaperSource, nResolution AS LONG

PROTECT ChosenPaperSize AS PaperSize

PROTECT ChosenPaperSource AS PaperSource

PROTECT ChosenResolution AS PrinterResolution

PROTECT acPrinters AS STRING[]

//---------- .Kind ?? -----------

PROTECT cOrientation, cColour, cDuplex, cMargins AS STRING

PROTECT AppFolder, ConfigName, ConfigFullPath AS STRING

PROTECT ConfigExists, IsDefault AS LOGIC

PROTECT StoredSettings AS STRING[]

PROTECT lReApply, lNoPrinter AS LOGIC

PROTECT DefaultPrinter, AppFilePath, VersionStamp AS STRING

PROTECT CreationTime AS DateTime

method Initialize() as void

LOCAL cFullPath AS STRING

SELF:ogbUserDefinedPageSettings:ForeColor := Color.FromArgb((int((Byte(213)))), (int((Byte(0)))), (int((Byte(0)))))

SELF:opbClose:ForeColor := Color.FromArgb((int(((System.Byte)(213)))), (int(((System.Byte)(0)))), (int(((System.Byte)(0)))))

SELF:olbDefaultPrinter:ForeColor := Color.FromArgb((int((Byte(213)))), (int((Byte(0)))), (int((Byte(0)))))

SELF:opbFindNDisplayDefault:ForeColor := Color.FromArgb((int((Byte(213)))), (int((Byte(0)))), (int((Byte(0)))))

SELF:opbExplain:BackColor := Color.FromArgb((int((Byte(0)))), (int((Byte(192)))), (int((Byte(0)))))

SELF:ConfigName := "ExaminePrinters.cfg"

SELF:AppFolder := Application.StartUpPath+"\"

cFullPath := SELF:AppFolder+SELF:ConfigName

SELF:ConfigFullPath := cFullPath

//MessageBox.Show(cFullPath)

IF File.Exists(cFullPath)

//MessageBox.Show("Config found - OK")

ELSE

MessageBox.Show("No Config exists !!!","CARE :")

File.Create(cFullPath)

ENDIF

lReApply := FALSE

cPrinterName := ""

self:olbxChosenSettings:ForeColor := Color.Blue

self:olbxChosenSettings:BackColor := Color.AliceBlue

ortbExaminePrinters:BackColor := Color.Beige

SELF:GetWinPrinters()

SELF:DoClearSettings()

ocbPreviewOnly:Checked := TRUE

lNoPrinter := TRUE

SELF:DoFindNDisplayDefault()

SELF:ShowCaptionVersionStamp()

SELF:MaximumSize := SELF:Size

//=================================

self:SetMyEventHandlers()

return

METHOD ShowCaptionVersionStamp() AS VOID

SELF:AppFilePath := Application.ExecutablePath

LOCAL oFI AS FileInfo

oFI := FileInfo{SELF:AppFilePath}

SELF:CreationTime := oFI:CreationTime

SELF:VersionStamp := SELF:CreationTime:ToShortDateString()+" : "+SELF:CreationTime:ToShortTimeString()

SELF:Text := "Examine Printers Tool : [ Version >> "+SELF:VersionStamp+" ]"

oFI := NULL_OBJECT

RETURN

METHOD DoFindNDisplayDefault() AS VOID

LOCAL oPS AS PrinterSettings

oPS := PrinterSettings{}

SELF:DefaultPrinter := oPS:PrinterName

oPS := NULL_OBJECT

olbDefaultPrinter:Text := "[ Default Printer >> "+SELF:DefaultPrinter+" ]"

cSelectedPrinter := SELF:DefaultPrinter

cPrinterName := cSelectedPrinter

lNoPrinter := FALSE

SELF:DoNewPrinterSelected()

ocbPrinters:SelectedItem := cSelectedPrinter

RETURN

METHOD DoNewPrinterSelected() AS VOID

SELF:ClearControls()

SELF:FillControls()

RETURN

METHOD FillControls() AS VOID

LOCAL oPS AS PrinterSettings

LOCAL nLen, i AS INT

LOCAL cLine AS STRING

aDS := STRING[]{18}

oSB := StringBuilder{}

oPS := PrinterSettings{}

oPS:PrinterName := cSelectedPrinter

SELF:IsDefault := FALSE

IF cSelectedPrinter == SELF:DefaultPrinter

SELF:IsDefault := TRUE

ENDIF

IF !oPS:IsValid

MessageBox.Show("Printer is not valid - check out problem.","WARNING !!!")

RETURN

ENDIF

//---------------------------------------

DPS := oPS:DefaultPageSettings

IF SELF:IsDefault

oSB:Append("Printer :- >> Is Default ! <<"+CRLF+CRLF)

ELSE

oSB:Append("Printer :- >> NOT Default <<"+CRLF+CRLF)

ENDIF

cLine := "Name : "+oPS:PrinterName:ToString()

aDS[1] := cLine

oSB:Append(cLine+CRLF)

cLine := "Max copies : "+oPS:MaximumCopies:ToString()

aDS[2] := cLine

oSB:Append(cLine+CRLF)

cLine := "LS angle : "+oPS:LandscapeAngle:ToString()

aDS[3] := cLine

oSB:Append(cLine+CRLF)

cLine := oPS:Duplex:ToString()

aDS[4] := cLine

oSB:Append(cLine+CRLF)

ocbDuplex:Enabled := FALSE

SELF:bDuplex := FALSE

IF !oPS:Duplex:ToString():Trim():ToUpper() = "SIMPLEX"

ocbDuplex:Enabled := TRUE

SELF:bDuplex := TRUE

ENDIF

cLine := "Copies : "+oPS:Copies:ToString()

aDS[5] := cLine

oSB:Append(cLine+CRLF)

cLine := "Collate : "+oPS:Collate:ToString()

aDS[6] := cLine

oSB:Append(cLine+CRLF)

cLine := "From Page : "+oPS:FromPage:ToString()

aDS[7] := cLine

oSB:Append(cLine+CRLF)

cLine := "To Page : "+oPS:ToPage:ToString()

aDS[8] := cLine

oSB:Append(cLine+CRLF)

cLine := "Max Page : "+oPS:MaximumPage:ToString()

aDS[9] := cLine

oSB:Append(cLine+CRLF)

cLine := "Min Page : "+oPS:MinimumPage:ToString()

aDS[10] := cLine

oSB:Append(cLine+CRLF)

cLine := "Print Range : "+oPS:PrintRange:ToString()

aDS[11] := cLine

oSB:Append(cLine+CRLF)

cLine := "Print to File : "+oPS:PrintToFile:ToString()

aDS[12] := cLine

oSB:Append(cLine+CRLF)

oSB:Append("======================================="+CRLF)

oSB:Append("Page :"+CRLF+CRLF)

cLine := "Paper size : "+DPS:PaperSize:PaperName

aDS[13] := cLine

oSB:Append(cLine+CRLF)

cLine := "Paper source : "+DPS:PaperSource:SourceName

aDS[14] := cLine

oSB:Append(cLine+CRLF)

cLine := "Resolution : "+DPS:PrinterResolution:Kind:ToString()

aDS[15] := cLine

oSB:Append(cLine+CRLF)

cLine := "Landscape : "+DPS:Landscape:ToString()

aDS[16] := cLine

oSB:Append(cLine+CRLF)

cLine := "Colour : "+DPS:Color:ToString()

aDS[17] := cLine

oSB:Append(cLine+CRLF)

ocbColour:Enabled := FALSE

IF DPS:Color

ocbColour:Enabled := TRUE

ENDIF

cLine := "Margins : "+DPS:Margins:ToString()

aDS[18] := cLine

oSB:Append(cLine+CRLF)

ortbExaminePrinters:Text := oSB:ToString()

//========================================

oSourceCollection := oPS:PaperSources

nLen := oSourceCollection:Count

SELF:ocbPaperSource:Items:Clear()

FOR i := 0 UPTO nLen-1

SELF:ocbPaperSource:Items:Add(oSourceCollection:Item[i]:SourceName)

NEXT // i

SELF:ocbPaperSource:SelectedIndex := 0

//--------------------------------------------------------------------

oSizeCollection := oPS:PaperSizes

nLen := oSizeCollection:Count

SELF:ocbPaperSize:Items:Clear()

FOR i := 0 UPTO nLen-1

SELF:ocbPaperSize:Items:Add(oSizeCollection:Item[i]:PaperName)

NEXT // i

SELF:ocbPaperSize:SelectedIndex := 0

//--------------------------------------------------------------------

oResolutionCollection := oPS:PrinterResolutions

nLen := oResolutionCollection:Count

SELF:ocbResolution:Items:Clear()

FOR i := 0 UPTO nLen-1

SELF:ocbResolution:Items:Add(oResolutionCollection:Item[i]:Kind:ToString())

NEXT // i

SELF:ocbResolution:SelectedIndex := 0

//--------------------------------------------------------------------

RETURN

METHOD ClearControls() AS VOID

SELF:DoClearSettings()

RETURN

METHOD DoClearSettings() AS VOID

SELF:ClearThreeCBs()

cPrinterName := "...."

cPaperSize := "...."

cPaperSource := "...."

cPrinterResolution := "...."

bLandscape := FALSE

bColor := FALSE

bDuplex := FALSE

cDuplex := "..."

cOrientation := "..."

cColour := "..."

nPaperSize := -1

nPaperSource := -1

nResolution := -1

SELF:DisplayChoices()

RETURN

METHOD DisplayChoices() AS VOID

LOCAL cLine, cIndex AS STRING

olbxChosenSettings:Items:Clear()

cLine := "Printer Name : "+cSelectedPrinter

olbxChosenSettings:Items:Add(cLine)

cIndex := ""

IF nPaperSize > -1

cIndex := "<<"+nPaperSize:ToString()+" >> "

ENDIF

cLine := "Paper Size : "+cIndex+cPaperSize

olbxChosenSettings:Items:Add(cLine)

cIndex := ""

IF nPaperSource > -1

cIndex := "<<"+nPaperSource:ToString()+" >> "

ENDIF

cLine := "Paper Source : "+cIndex+cPaperSource

olbxChosenSettings:Items:Add(cLine)

cIndex := ""

IF nResolution > -1

cIndex := "<<"+nResolution:ToString()+" >> "

ENDIF

cLine := "Printer Resolution : "+cIndex+cPrinterResolution

olbxChosenSettings:Items:Add(cLine)

cLine := "Orientation : "+cOrientation

olbxChosenSettings:Items:Add(cLine)

cLine := "Colour : "+cColour

olbxChosenSettings:Items:Add(cLine)

cLine := "Duplex : "+cDuplex

olbxChosenSettings:Items:Add(cLine)

RETURN

METHOD ClearThreeCBs() AS VOID

ocbDuplex:Checked := FALSE

ocbColour:Checked := FALSE

ocbLandscape:Checked := FALSE

ocbDuplex:Enabled := FALSE

ocbColour:Enabled := FALSE

RETURN

METHOD GetWinPrinters() AS VOID

LOCAL oPSSC AS PrinterSettings.StringCollection

oPSSC := PrinterSettings.InstalledPrinters

acPrinters := STRING[]{oPSSC:Count}

oPSSC:CopyTo(acPrinters,0)

SELF:ocbPrinters:Items:Clear()

SELF:ocbPrinters:Items:AddRange(acPrinters)

SELF:ocbPrinters:SelectedIndex := 0

RETURN

method SetMyEventHandlers() as void

SELF:opbClose:Click += System.EventHandler{ SELF, @opbClose_Click() }

//-------------------------------------------------------------------

SELF:ocbPrinters:SelectionChangeCommitted += System.EventHandler{ SELF, @ocbPrinters_SelectionChangeCommitted() }

SELF:ocbPaperSize:SelectionChangeCommitted += System.EventHandler{ SELF, @ocbPaperSize_SelectionChangeCommitted() }

SELF:ocbPaperSource:SelectionChangeCommitted += System.EventHandler{ SELF, @ocbPaperSource_SelectionChangeCommitted() }

SELF:ocbResolution:SelectionChangeCommitted += System.EventHandler{ SELF, @ocbResolution_SelectionChangeCommitted() }

//------------------------------------------------------------------------------------------------

SELF:opbClearChosenSettings:Click += System.EventHandler{ SELF, @opbClearChosenSettings_Click() }

SELF:opbDoTestPrint:Click += System.EventHandler{ SELF, @opbDoTestPrint_Click() }

SELF:opbCheckPCforA4:Click += System.EventHandler{ SELF, @opbCheckPCforA4_Click() }

//------------------------------------------------------------------------------------------

SELF:opbSaveUserSettings:Click += System.EventHandler{ SELF, @opbSaveUserSettings_Click() }

SELF:opbViewUserSettings:Click += System.EventHandler{ SELF, @opbViewUserSettings_Click() }

SELF:opbApplyStoredSettings:Click += System.EventHandler{ SELF, @opbApplyStoredSettings_Click() }

//--------------------------------------------------------------------------------------------------

SELF:ocbLandscape:CheckedChanged += System.EventHandler{ SELF, @ocbLandscape_CheckedChanged() }

SELF:ocbColour:CheckedChanged += System.EventHandler{ SELF, @ocbColour_CheckedChanged() }

SELF:ocbDuplex:CheckedChanged += System.EventHandler{ SELF, @ocbDuplex_CheckedChanged() }

//--------------------------------------------------------------------------------------------------

SELF:oReadBackOnDoPrint:CheckedChanged += System.EventHandler{ SELF, @oReadBackOnDoPrint_CheckedChanged() }

SELF:opbFindNDisplayDefault:Click += System.EventHandler{ SELF, @opbFindNDisplayDefault_Click() }

//-------------------------------------------------------------------------------------------------

SELF:opbExplain:Click += System.EventHandler{ SELF, @opbExplain_Click() }

return

PRIVATE METHOD opbExplain_Click( sender AS System.Object, e AS System.EventArgs ) AS System.Void

self:DoExplainNotes()

RETURN

method DoExplainNotes() as void

local cText, cR, cR2 as string

cR := CRLF

cR2 := cR+cR

cText := ""

cText += "Exploring Printers and Their Settings :"+cR2

cText += "Find all printers on a PC system."+cR

cText += "Access all possible settings for a target printer."+cR

cText += "Select,apply and use these settings."+cR

cText += "Save settings for a target printer."+cR

cText += "Read in and re-apply settings for a printer."+cR

cText += "Check paper size settigs across all available printers."+cR

cText += "Both local and network printers are accessed."+cR

cText += "Do print tests on a target printer - orientation, colour, duplexing."+cR2

cText += "* NOTE * - default settings can't be over-written."+cR

cText += "[Otherwise you could affect printing for others over a network.]"+cR

cText += "Permanent changes need to be done at a system level."+cR

cText += "For this use Control Panel, Printers, right click properties,"+cR

cText += "then Printing Preferences - Media Size (for example)."+cR

MessageBox.Show(cText,"Explain : ")

return

PRIVATE METHOD oReadBackOnDoPrint_CheckedChanged( sender AS System.Object, e AS System.EventArgs ) AS System.Void

//----- set flag to read back settings and show ------

RETURN

PRIVATE METHOD ocbDuplex_CheckedChanged( sender AS System.Object, e AS System.EventArgs ) AS System.Void

//-------- set the value of the Duplex selection ---------

IF ocbDuplex:Checked

bDuplex := TRUE

cDuplex := "Yes"

ELSE

bDuplex := FALSE

cDuplex := "No"

ENDIF

SELF:DisplayChoices()

RETURN

PRIVATE METHOD ocbColour_CheckedChanged( sender AS System.Object, e AS System.EventArgs ) AS System.Void

//-------- set the value of the Colour selection ---------

IF ocbColour:Checked

bColor := TRUE

cColour := "Yes"

ELSE

bColor := FALSE

cColour := "No"

ENDIF

SELF:DisplayChoices()

RETURN

PRIVATE METHOD ocbLandscape_CheckedChanged( sender AS System.Object, e AS System.EventArgs ) AS System.Void

//------ set the value of the Landscape selection ------

IF ocbLandscape:Checked

bLandscape := TRUE

cOrientation := "Landscape"

ELSE

bLandscape := FALSE

cOrientation := "Portrait"

ENDIF

SELF:DisplayChoices()

RETURN

PRIVATE METHOD ocbResolution_SelectionChangeCommitted( sender AS System.Object, e AS System.EventArgs ) AS System.Void

//-------- select a media weight ---------

cPrinterResolution := SELF:ocbResolution:SelectedItem:ToString()

nResolution := SELF:ocbResolution:SelectedIndex

SELF:DisplayChoices()

RETURN

PRIVATE METHOD ocbPaperSource_SelectionChangeCommitted( sender AS System.Object, e AS System.EventArgs ) AS System.Void

//--------- select a paper source ---------

cPaperSource := SELF:ocbPaperSource:SelectedItem:ToString()

nPaperSource := SELF:ocbPaperSource:SelectedIndex

SELF:DisplayChoices()

RETURN

PRIVATE METHOD ocbPaperSize_SelectionChangeCommitted( sender AS System.Object, e AS System.EventArgs ) AS System.Void

//-------- select a paper size ---------

cPaperSize := SELF:ocbPaperSize:SelectedItem:ToString()

nPaperSize := SELF:ocbPaperSize:SelectedIndex

SELF:DisplayChoices()

RETURN

PRIVATE METHOD ocbPrinters_SelectionChangeCommitted( sender AS System.Object, e AS System.EventArgs ) AS System.Void

//-------- when a printer is selected --------

SELF:DoPrinterSelected()

RETURN

PRIVATE METHOD opbApplyStoredSettings_Click( sender AS System.Object, e AS System.EventArgs ) AS System.Void

self:DoApplyStoredSettings()

RETURN

METHOD DoApplyStoredSettings() AS VOID

LOCAL oSR AS StreamReader

LOCAL cText, cLine AS STRING

LOCAL i AS DWORD

LOCAL lAOK AS LOGIC

lReApply := TRUE

SELF:ClearControls()

cText := ""

//------ force stored settings from file - if valid ! -------

SELF:StoredSettings := STRING[]{10}

oSR := StreamReader{SELF:ConfigFullPath}

FOR i := 1 UPTO 10

cLine := oSR:ReadLine()

IF cLine == NULL_OBJECT

EXIT

ENDIF

SELF:StoredSettings[i] := cLine

cText += cLine+CRLF

NEXT

oSR:Close()

oSR:Dispose()

MessageBox.Show(cText,"View : Stored Settings :")

lAOK := TRUE

FOR i := 1 UPTO 10

cLine := SELF:StoredSettings[i]:Trim()

IF !SELF:DecodeSettingsAndApply(cLine)

lAOK := FALSE

ELSE

IF i = 1

SELF:DoPrinterSelected()

ENDIF

ENDIF

NEXT

cText := ""

IF lAOK

cText := "Yes, all ten done OK !"

ELSE

cText := "At least one invalid !"

ENDIF

MessageBox.Show(cText,"Validation Results :")

SELF:DisplayChoices()

SELF:FillControls()

ocbPaperSize:SelectedItem := cPaperSize

ocbPaperSource:SelectedItem := cPaperSource

ocbResolution:SelectedItem := cPrinterResolution

ocbDuplex:Checked := bDuplex

ocbColour:Checked := bColor

ocbLandscape:Checked := bLandscape

RETURN

METHOD DoPrinterSelected() AS VOID

self:Cursor := Cursors.WaitCursor

IF !lReApply

cSelectedPrinter := SELF:ocbPrinters:SelectedItem:ToString()

ENDIF

cPrinterName := cSelectedPrinter

SELF:DoNewPrinterSelected()

SELF:DisplayChoices()

IF lReApply

ocbPrinters:SelectedItem := cSelectedPrinter

ENDIF

lReApply := FALSE

lNoPrinter := FALSE

self:Cursor := Cursors.Default

RETURN

METHOD DecodeSettingsAndApply(cLine AS STRING) AS LOGIC

LOCAL lResult AS LOGIC

LOCAL nLen AS INT

LOCAL cLeft, cRight AS STRING

LOCAL nIndex AS LONG

lResult := FALSE

cLine := cLine:Trim()

nLen := cLine:Length

IF !cLine:Contains(":")

RETURN lResult

ENDIF

nIndex := cLine:IndexOf(":")

cLeft := cLine:Substring(0,nIndex+1):Trim()

cRight := cLine:Substring(nIndex+1,nLen-nIndex-1):Trim()

//MessageBox.Show(cLeft:ToString()+CRLF+cRight:ToString())

lResult := TRUE

DO CASE

CASE cLeft == "Printer Name :"

cSelectedPrinter := cRight

CASE cleft == "Paper Size :"

cPaperSize := cRight

CASE cleft == "Paper Source :"

cPaperSource := cRight

CASE cleft == "Printer Resolution :"

cPrinterResolution := cRight

CASE cleft == "Colour :"

IF cRight == "TRUE"

bColor := TRUE

cColour := "Yes"

ELSE

bColor := FALSE

cColour := "No"

ENDIF

CASE cleft == "Orientation :"

IF cRight == "Landscape"

bLandscape := TRUE

cOrientation := "Landscape"

ELSE

bLandscape := FALSE

cOrientation := "Portrait"

ENDIF

CASE cleft == "Duplex :"

IF cRight == "TRUE"

bDuplex := TRUE

cDuplex := "Yes"

ELSE

bDuplex := FALSE

cDuplex := "No"

ENDIF

CASE cleft == "N Size :"

nPaperSize := (INT)Conversion.Val(cRight)

CASE cleft == "N Source :"

nPaperSource := (INT)Conversion.Val(cRight)

CASE cleft == "N Resolution :"

nResolution := (INT)Conversion.Val(cRight)

OTHERWISE

lResult := FALSE

END CASE

RETURN lResult

PRIVATE METHOD opbViewUserSettings_Click( sender AS System.Object, e AS System.EventArgs ) AS System.Void

self:DoViewUserSettingsClick()

RETURN

METHOD DoViewUserSettingsClick() AS VOID

LOCAL oSR AS StreamReader

LOCAL cText, cLine AS STRING

LOCAL i AS DWORD

cText := ""

//-------- read and show stored settings ---------

SELF:StoredSettings := STRING[]{10}

oSR := StreamReader{SELF:ConfigFullPath}

FOR i := 1 UPTO 10

cLine := oSR:ReadLine()

IF cLine == NULL_OBJECT

EXIT

ENDIF

SELF:StoredSettings[i] := cLine

cText += cLine+CRLF

NEXT

oSR:Close()

oSR:Dispose()

MessageBox.Show(cText,"View : Stored Settings :")

RETURN

PRIVATE METHOD opbSaveUserSettings_Click( sender AS System.Object, e AS System.EventArgs ) AS System.Void

self:DoSaveUserSettings()

RETURN

METHOD DoSaveUserSettings() AS VOID

LOCAL oSW AS StreamWriter

LOCAL oSB AS StringBuilder

oSB := StringBuilder{}

//------- save selected settings to file (cfg) --------

oSB:Append("Printer Name : "+cSelectedPrinter+CRLF)

oSB:Append("Paper Size : "+cPaperSize+CRLF)

oSB:Append("Paper Source : "+cPaperSource+CRLF)

oSB:Append("Printer Resolution : "+cPrinterResolution+CRLF)

IF bColor

oSB:Append("Colour : TRUE"+CRLF)

ELSE

oSB:Append("Colour : FALSE"+CRLF)

ENDIF

IF bLandscape

oSB:Append("Orientation : Landscape"+CRLF)

ELSE

oSB:Append("Orientation : Portrait"+CRLF)

ENDIF

IF bDuplex

oSB:Append("Duplex : TRUE"+CRLF)

ELSE

oSB:Append("Duplex : FALSE"+CRLF)

ENDIF

oSB:Append("N Size : "+nPaperSize:ToString()+CRLF)

oSB:Append("N Source : "+nPaperSource:ToString()+CRLF)

oSB:Append("N Resolution : "+nResolution:ToString()+CRLF)

oSW := StreamWriter{SELF:ConfigFullPath, FALSE, Encoding.ASCII}

oSW:Write(oSB:ToString())

oSW:Close()

oSW:Dispose()

RETURN

PRIVATE METHOD opbCheckPCforA4_Click( sender AS System.Object, e AS System.EventArgs ) AS System.Void

//-------- look at all printers for A4 paper size ---------

SELF:LookAtAllPrintersForA4()

RETURN

METHOD LookAtAllPrintersForA4() AS VOID

self:Cursor := Cursors.WaitCursor

LOCAL i, nLen AS INT

LOCAL cPrinter, cText AS STRING

cText := ""

nLen := acPrinters:Length

FOR i := 1 UPTO nLen

cPrinter := acPrinters[i]:ToString()

cText += SELF:GetDefaultPaperSize(cPrinter)+" >> "+cPrinter+CRLF

NEXT

self:Cursor := Cursors.Default

MessageBox.Show(cText,"Paper Sizes : Default Settings :")

RETURN

METHOD GetDefaultPaperSize(cPrinter AS STRING) AS STRING

LOCAL cResult AS STRING

LOCAL oPS AS PrinterSettings

cResult := "... not found ..."

oPS := PrinterSettings{}

oPS:PrinterName := cPrinter

IF !oPS:IsValid

MessageBox.Show("Printer is not valid - check out problem.","WARNING !!!")

RETURN cResult

ENDIF

//---------------------------------------

cResult := oPS:DefaultPageSettings:PaperSize:PaperName

RETURN cResult

PRIVATE METHOD opbDoTestPrint_Click( sender AS System.Object, e AS System.EventArgs ) AS System.Void

self:DoTestPrintClick()

RETURN

METHOD DoTestPrintClick() AS VOID

//------- force the print of a test document - with full details on ! -------

IF lNoPrinter

MessageBox.Show("First select a printer !","No Printer Chosen :")

RETURN

ENDIF

IF !ocbApplyUserSettings:Checked

MessageBox.Show("Output will be to Default printer !!!","User Settings NOT Applied :")

ENDIF

LOCAL oDocument AS PrintDocument

LOCAL oPreview AS PrintPreviewDialog

oDocument := PrintDocument{}

oDocument:PrintPage += PrintPageEventHandler{SELF, @TestPagePrint()}

IF ocbApplyUserSettings:Checked

SELF:ApplyChosenSettings(oDocument)

ENDIF

IF ocbPreviewOnly:Checked

oPreview := PrintPreviewDialog{}

oPreview:Document := oDocument

oPreview:PrintPreviewControl:AutoZoom := TRUE

oPreview:ShowDialog()

ELSE

oDocument:Print()

ENDIF

oDocument:PrintPage -= PrintPageEventHandler{SELF, @TestPagePrint()}

oDocument := NULL_OBJECT

RETURN

METHOD ApplyChosenSettings(oDoc AS PrintDocument) AS VOID

LOCAL oPS AS PrinterSettings

LOCAL cText AS STRING

cText := ""

oPS := PrinterSettings{}

oPS:PrinterName := cSelectedPrinter

//---------------------------------

oDoc:PrinterSettings:PrinterName := cSelectedPrinter

IF nPaperSize > -1

oDoc:DefaultPageSettings:PaperSize := oPS:PaperSizes[nPaperSize]

cText += ">> Paper Size : "+oDoc:DefaultPageSettings:PaperSize:PaperName:ToString()+CRLF

ENDIF

IF nPaperSource > -1

oDoc:DefaultPageSettings:PaperSource := oPS:PaperSources[nPaperSource]

cText += ">> Paper Source : "+oDoc:DefaultPageSettings:PaperSource:SourceName:ToString()+CRLF

ENDIF

IF nResolution > -1

oDoc:DefaultPageSettings:PrinterResolution := oPS:PrinterResolutions[nResolution]

cText += ">> Resolution : "+oDoc:DefaultPageSettings:PrinterResolution:Kind:ToString()+CRLF

ENDIF

IF ocbLandscape:Checked

oDoc:DefaultPageSettings:Landscape := TRUE

cText += ">> Orientation : Landscape"+CRLF

ELSE

oDoc:DefaultPageSettings:Landscape := FALSE

cText += ">> Orientation : Portrait"+CRLF

ENDIF

IF ocbColour:Checked

oDoc:DefaultPageSettings:Color := TRUE

cText += ">> Colour : yes"+CRLF

ELSE

oDoc:DefaultPageSettings:Color := FALSE

cText += ">> Colour : no"+CRLF

ENDIF

IF ocbDuplex:Checked

IF ocbLandscape:Checked

oDoc:PrinterSettings:Duplex := Duplex.Horizontal

cText += ">> Duplex : Horizontal "+CRLF

ELSE

oDoc:PrinterSettings:Duplex := Duplex.Vertical

cText += ">> Duplex : Vertical "+CRLF

ENDIF

ELSE

oDoc:PrinterSettings:Duplex := Duplex.Simplex

cText += ">> Simplex : yes "+CRLF

ENDIF

IF oReadBackOnDoPrint:Checked

MessageBox.Show(cText,"Applied Settings : For Test Print :")

ENDIF

RETURN

METHOD TestPagePrint(o AS OBJECT , e AS PrintPageEventArgs) AS VOID

LOCAL oFont AS Font

LOCAL oBrush AS SolidBrush

//LOCAL x,y,nField, i AS DWORD

LOCAL x,y, i AS DWORD

LOCAL cLine, cDate, cTime AS STRING

LOCAL oDT AS DateTime

oDT := DateTime.Now

oFont := Font{"Arial" , 10.0}

oBrush := SolidBrush{Color.Black}

y := 50

x := 50

IF SELF:IsDefault

cLine := "Examine Printers Test:- << Is Default ! >>"

ELSE

cLine := "Examine Printers Test:- << NOT Default >>"

ENDIF

e:Graphics:DrawString(cLine , oFont , oBrush , x , y)

y += oFont:Height

y += oFont:Height

cDate := oDT:ToLongDateString()

cTime := oDT:ToLongTimeString()

cLine := "Date : "+cDate+" Time : "+cTime

e:Graphics:DrawString(cLine , oFont , oBrush , x , y)

y += oFont:Height

cLine := "================================================"

e:Graphics:DrawString(cLine , oFont , oBrush , x , y)

y += oFont:Height

//===================================================

oBrush := SolidBrush{Color.Blue}

cLine := "Chosen Settings Below :-"

e:Graphics:DrawString(cLine , oFont , oBrush , x , y)

y += oFont:Height

y += oFont:Height

cLine := "Printer :- "+cSelectedPrinter

e:Graphics:DrawString(cLine , oFont , oBrush , x , y)

y += oFont:Height

cLine := "Paper Size :- "+cPaperSize

e:Graphics:DrawString(cLine , oFont , oBrush , x , y)

y += oFont:Height

cLine := "Paper Source :- "+cPaperSource

e:Graphics:DrawString(cLine , oFont , oBrush , x , y)

y += oFont:Height

cLine := "Resolution :- "+cPrinterResolution

e:Graphics:DrawString(cLine , oFont , oBrush , x , y)

y += oFont:Height

cLine := "Landscape :- "+bLandscape:ToString()

e:Graphics:DrawString(cLine , oFont , oBrush , x , y)

y += oFont:Height

cLine := "Colour :- "+bColor:ToString()

e:Graphics:DrawString(cLine , oFont , oBrush , x , y)

y += oFont:Height

cLine := "Duplex :- "+bDuplex:ToString()

e:Graphics:DrawString(cLine , oFont , oBrush , x , y)

y += oFont:Height

y += oFont:Height

//=======================================================

oBrush := SolidBrush{Color.Red}

cLine := "Default Settings :-"

e:Graphics:DrawString(cLine , oFont , oBrush , x , y)

y += oFont:Height

y += oFont:Height

cLine := "PAGE :-"

e:Graphics:DrawString(cLine , oFont , oBrush , x , y)

y += oFont:Height

FOR i := 13 UPTO 18

cLine := " "+aDS[i]:ToString()

e:Graphics:DrawString(cLine , oFont , oBrush , x , y)

y += oFont:Height

NEXT // i

oBrush := SolidBrush{Color.Green}

//--------------------------

y += oFont:Height

cLine := "PRINTER :-"

e:Graphics:DrawString(cLine , oFont , oBrush , x , y)

y += oFont:Height

FOR i := 1 UPTO 12

cLine := " "+aDS[i]:ToString()

e:Graphics:DrawString(cLine , oFont , oBrush , x , y)

y += oFont:Height

NEXT // i

e:HasMorePages := FALSE

RETURN

PRIVATE METHOD opbClearChosenSettings_Click( sender AS System.Object, e AS System.EventArgs ) AS System.Void

//-------- clears the chosen settings (list plus) ---------

SELF:DoClearSettings()

RETURN

PRIVATE METHOD opbFindNDisplayDefault_Click( sender AS System.Object, e AS System.EventArgs ) AS System.Void

RETURN

PRIVATE METHOD fmExaminePrintersTool_Load( sender AS System.Object, e AS System.EventArgs ) AS System.Void

SELF:DoFindNDisplayDefault()

RETURN

PRIVATE METHOD opbClose_Click( sender AS System.Object, e AS System.EventArgs ) AS System.Void

self:DoClose()

RETURN

method DoClose() as void

self:Close()

return

end class

===============================[ExaminePrintersToolVS.prg]===============================

PARTIAL CLASS fmExaminePrintersTool INHERIT System.Windows.Forms.Form

PRIVATE opbClose AS System.Windows.Forms.Button

PRIVATE ogbUserDefinedPageSettings AS System.Windows.Forms.GroupBox

PRIVATE ocbPaperSize AS System.Windows.Forms.ComboBox

PRIVATE ocbPaperSource AS System.Windows.Forms.ComboBox

PRIVATE olbPaperSource AS System.Windows.Forms.Label

PRIVATE olbPaperSize AS System.Windows.Forms.Label

PRIVATE ocbResolution AS System.Windows.Forms.ComboBox

PRIVATE olbResolution AS System.Windows.Forms.Label

PRIVATE ocbLandscape AS System.Windows.Forms.CheckBox

PRIVATE ocbDuplex AS System.Windows.Forms.CheckBox

PRIVATE ocbColour AS System.Windows.Forms.CheckBox

PRIVATE olbWindowsPrinters AS System.Windows.Forms.Label

PRIVATE ocbPrinters AS System.Windows.Forms.ComboBox

PRIVATE opbIsPrinterValid AS System.Windows.Forms.Button

PRIVATE ortbExaminePrinters AS System.Windows.Forms.RichTextBox

PRIVATE olbDefaultPageSettings AS System.Windows.Forms.Label

PRIVATE olbxChosenSettings AS System.Windows.Forms.ListBox

PRIVATE olbChosen AS System.Windows.Forms.Label

PRIVATE ocbPreviewOnly AS System.Windows.Forms.CheckBox

PRIVATE olbDefaultPrinter AS System.Windows.Forms.Label

PRIVATE opbFindNDisplayDefault AS System.Windows.Forms.Button

PRIVATE opbClearChosenSettings AS System.Windows.Forms.Button

PRIVATE ocbApplyUserSettings AS System.Windows.Forms.CheckBox

PRIVATE oReadBackOnDoPrint AS System.Windows.Forms.CheckBox

PRIVATE opbDoTestPrint AS System.Windows.Forms.Button

PRIVATE opbCheckPCforA4 AS System.Windows.Forms.Button

PRIVATE oPictureBox1 AS System.Windows.Forms.PictureBox

PRIVATE oPictureBox2 AS System.Windows.Forms.PictureBox

PRIVATE opbSaveUserSettings AS System.Windows.Forms.Button

PRIVATE opbViewUserSettings AS System.Windows.Forms.Button

PRIVATE opbApplyStoredSettings AS System.Windows.Forms.Button

PRIVATE opbExplain AS System.Windows.Forms.Button

PRIVATE components AS System.ComponentModel.IContainer

CONSTRUCTOR()

SUPER()

SELF:InitializeComponent()

self:Initialize()

RETURN

/// <summary>

/// Clean up any resources being used.

/// </summary>

/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>

PROTECTED VIRTUAL METHOD Dispose( disposing AS System.Boolean ) AS System.Void

IF disposing && components != NULL

components:Dispose()

ENDIF

SUPER:Dispose( disposing )

RETURN

/// <summary>

/// Required method for Designer support - do not modify

/// the contents of this method with the code editor.

/// </summary>

PRIVATE METHOD InitializeComponent() AS System.Void

LOCAL resources := System.ComponentModel.ComponentResourceManager{typeof(fmExaminePrintersTool)} AS System.ComponentModel.ComponentResourceManager

SELF:opbClose := System.Windows.Forms.Button{}

SELF:ogbUserDefinedPageSettings := System.Windows.Forms.GroupBox{}

SELF:ocbDuplex := System.Windows.Forms.CheckBox{}

SELF:ocbColour := System.Windows.Forms.CheckBox{}

SELF:ocbLandscape := System.Windows.Forms.CheckBox{}

SELF:ocbResolution := System.Windows.Forms.ComboBox{}

SELF:olbResolution := System.Windows.Forms.Label{}

SELF:ocbPaperSource := System.Windows.Forms.ComboBox{}

SELF:olbPaperSource := System.Windows.Forms.Label{}

SELF:olbPaperSize := System.Windows.Forms.Label{}

SELF:ocbPaperSize := System.Windows.Forms.ComboBox{}

SELF:olbWindowsPrinters := System.Windows.Forms.Label{}

SELF:ocbPrinters := System.Windows.Forms.ComboBox{}

SELF:opbIsPrinterValid := System.Windows.Forms.Button{}

SELF:ortbExaminePrinters := System.Windows.Forms.RichTextBox{}

SELF:olbDefaultPageSettings := System.Windows.Forms.Label{}

SELF:olbDefaultPrinter := System.Windows.Forms.Label{}

SELF:olbxChosenSettings := System.Windows.Forms.ListBox{}

SELF:olbChosen := System.Windows.Forms.Label{}

SELF:ocbPreviewOnly := System.Windows.Forms.CheckBox{}

SELF:opbFindNDisplayDefault := System.Windows.Forms.Button{}

SELF:opbClearChosenSettings := System.Windows.Forms.Button{}

SELF:ocbApplyUserSettings := System.Windows.Forms.CheckBox{}

SELF:oReadBackOnDoPrint := System.Windows.Forms.CheckBox{}

SELF:opbDoTestPrint := System.Windows.Forms.Button{}

SELF:opbCheckPCforA4 := System.Windows.Forms.Button{}

SELF:oPictureBox1 := System.Windows.Forms.PictureBox{}

SELF:oPictureBox2 := System.Windows.Forms.PictureBox{}

SELF:opbSaveUserSettings := System.Windows.Forms.Button{}

SELF:opbViewUserSettings := System.Windows.Forms.Button{}

SELF:opbApplyStoredSettings := System.Windows.Forms.Button{}

SELF:opbExplain := System.Windows.Forms.Button{}

SELF:ogbUserDefinedPageSettings:SuspendLayout()

((System.ComponentModel.ISupportInitialize)(SELF:oPictureBox1)):BeginInit()

((System.ComponentModel.ISupportInitialize)(SELF:oPictureBox2)):BeginInit()

SELF:SuspendLayout()

//

// opbClose

//

SELF:opbClose:BackColor := System.Drawing.Color.Beige

SELF:opbClose:Location := System.Drawing.Point{804, 534}

SELF:opbClose:Name := "opbClose"

SELF:opbClose:Size := System.Drawing.Size{72, 24}

SELF:opbClose:TabIndex := 0

SELF:opbClose:Text := "Close"

SELF:opbClose:UseVisualStyleBackColor := FALSE

//

// ogbUserDefinedPageSettings

//

SELF:ogbUserDefinedPageSettings:Controls:Add(SELF:ocbDuplex)

SELF:ogbUserDefinedPageSettings:Controls:Add(SELF:ocbColour)

SELF:ogbUserDefinedPageSettings:Controls:Add(SELF:ocbLandscape)

SELF:ogbUserDefinedPageSettings:Controls:Add(SELF:ocbResolution)

SELF:ogbUserDefinedPageSettings:Controls:Add(SELF:olbResolution)

SELF:ogbUserDefinedPageSettings:Controls:Add(SELF:ocbPaperSource)

SELF:ogbUserDefinedPageSettings:Controls:Add(SELF:olbPaperSource)

SELF:ogbUserDefinedPageSettings:Controls:Add(SELF:olbPaperSize)

SELF:ogbUserDefinedPageSettings:Controls:Add(SELF:ocbPaperSize)

SELF:ogbUserDefinedPageSettings:Location := System.Drawing.Point{516, 12}

SELF:ogbUserDefinedPageSettings:Name := "ogbUserDefinedPageSettings"

SELF:ogbUserDefinedPageSettings:Size := System.Drawing.Size{354, 294}

SELF:ogbUserDefinedPageSettings:TabIndex := 1

SELF:ogbUserDefinedPageSettings:TabStop := FALSE

SELF:ogbUserDefinedPageSettings:Text := "User Defined Page Settings :"

//

// ocbDuplex

//

SELF:ocbDuplex:Location := System.Drawing.Point{150, 252}

SELF:ocbDuplex:Name := "ocbDuplex"

SELF:ocbDuplex:Size := System.Drawing.Size{72, 24}

SELF:ocbDuplex:TabIndex := 8

SELF:ocbDuplex:Text := "Duplex :"

SELF:ocbDuplex:UseVisualStyleBackColor := TRUE

//

// ocbColour

//

SELF:ocbColour:Location := System.Drawing.Point{150, 216}

SELF:ocbColour:Name := "ocbColour"

SELF:ocbColour:Size := System.Drawing.Size{104, 24}

SELF:ocbColour:TabIndex := 7

SELF:ocbColour:Text := "Colour :"

SELF:ocbColour:UseVisualStyleBackColor := TRUE

//

// ocbLandscape

//

SELF:ocbLandscape:Location := System.Drawing.Point{150, 180}

SELF:ocbLandscape:Name := "ocbLandscape"

SELF:ocbLandscape:Size := System.Drawing.Size{104, 24}

SELF:ocbLandscape:TabIndex := 6

SELF:ocbLandscape:Text := "Landscape :"

SELF:ocbLandscape:UseVisualStyleBackColor := TRUE

//

// ocbResolution

//

SELF:ocbResolution:FormattingEnabled := TRUE

SELF:ocbResolution:Location := System.Drawing.Point{132, 138}

SELF:ocbResolution:Name := "ocbResolution"

SELF:ocbResolution:Size := System.Drawing.Size{192, 21}

SELF:ocbResolution:TabIndex := 5

//

// olbResolution

//

SELF:olbResolution:Location := System.Drawing.Point{30, 138}

SELF:olbResolution:Name := "olbResolution"

SELF:olbResolution:Size := System.Drawing.Size{84, 23}

SELF:olbResolution:TabIndex := 4

SELF:olbResolution:Text := "Resolution :"

//

// ocbPaperSource

//

SELF:ocbPaperSource:FormattingEnabled := TRUE

SELF:ocbPaperSource:Location := System.Drawing.Point{132, 90}

SELF:ocbPaperSource:Name := "ocbPaperSource"

SELF:ocbPaperSource:Size := System.Drawing.Size{192, 21}

SELF:ocbPaperSource:TabIndex := 3

//

// olbPaperSource

//

SELF:olbPaperSource:Location := System.Drawing.Point{30, 90}

SELF:olbPaperSource:Name := "olbPaperSource"

SELF:olbPaperSource:Size := System.Drawing.Size{90, 23}

SELF:olbPaperSource:TabIndex := 2

SELF:olbPaperSource:Text := "Paper Source :"

//

// olbPaperSize

//

SELF:olbPaperSize:Location := System.Drawing.Point{30, 42}

SELF:olbPaperSize:Name := "olbPaperSize"

SELF:olbPaperSize:Size := System.Drawing.Size{78, 23}

SELF:olbPaperSize:TabIndex := 1

SELF:olbPaperSize:Text := "Paper Size :"

//

// ocbPaperSize

//

SELF:ocbPaperSize:FormattingEnabled := TRUE

SELF:ocbPaperSize:Location := System.Drawing.Point{132, 42}

SELF:ocbPaperSize:Name := "ocbPaperSize"

SELF:ocbPaperSize:Size := System.Drawing.Size{192, 21}

SELF:ocbPaperSize:TabIndex := 0

//

// olbWindowsPrinters

//

SELF:olbWindowsPrinters:Location := System.Drawing.Point{12, 12}

SELF:olbWindowsPrinters:Name := "olbWindowsPrinters"

SELF:olbWindowsPrinters:Size := System.Drawing.Size{100, 23}

SELF:olbWindowsPrinters:TabIndex := 2

SELF:olbWindowsPrinters:Text := "Windows Printers :"

//

// ocbPrinters

//

SELF:ocbPrinters:FormattingEnabled := TRUE

SELF:ocbPrinters:Location := System.Drawing.Point{114, 12}

SELF:ocbPrinters:Name := "ocbPrinters"

SELF:ocbPrinters:Size := System.Drawing.Size{240, 21}

SELF:ocbPrinters:TabIndex := 3

//

// opbIsPrinterValid

//

SELF:opbIsPrinterValid:Location := System.Drawing.Point{378, 36}

SELF:opbIsPrinterValid:Name := "opbIsPrinterValid"

SELF:opbIsPrinterValid:Size := System.Drawing.Size{90, 23}

SELF:opbIsPrinterValid:TabIndex := 4

SELF:opbIsPrinterValid:Text := "Is Printer Valid"

SELF:opbIsPrinterValid:UseVisualStyleBackColor := TRUE

SELF:opbIsPrinterValid:Visible := FALSE

//

// ortbExaminePrinters

//

SELF:ortbExaminePrinters:Location := System.Drawing.Point{12, 90}

SELF:ortbExaminePrinters:Name := "ortbExaminePrinters"

SELF:ortbExaminePrinters:Size := System.Drawing.Size{456, 330}

SELF:ortbExaminePrinters:TabIndex := 5

SELF:ortbExaminePrinters:Text := "Default Page Settings will be displayed ...."

//

// olbDefaultPageSettings

//

SELF:olbDefaultPageSettings:Location := System.Drawing.Point{12, 66}

SELF:olbDefaultPageSettings:Name := "olbDefaultPageSettings"

SELF:olbDefaultPageSettings:Size := System.Drawing.Size{162, 23}

SELF:olbDefaultPageSettings:TabIndex := 6

SELF:olbDefaultPageSettings:Text := "Default Printer / Page Settings :"

//

// olbDefaultPrinter

//

SELF:olbDefaultPrinter:Location := System.Drawing.Point{180, 66}

SELF:olbDefaultPrinter:Name := "olbDefaultPrinter"

SELF:olbDefaultPrinter:Size := System.Drawing.Size{330, 23}

SELF:olbDefaultPrinter:TabIndex := 7

SELF:olbDefaultPrinter:Text := "{ ... default printer ... ]"

//

// olbxChosenSettings

//

SELF:olbxChosenSettings:FormattingEnabled := TRUE

SELF:olbxChosenSettings:Location := System.Drawing.Point{186, 432}

SELF:olbxChosenSettings:Name := "olbxChosenSettings"

SELF:olbxChosenSettings:Size := System.Drawing.Size{282, 121}

SELF:olbxChosenSettings:TabIndex := 8

//

// olbChosen

//

SELF:olbChosen:Location := System.Drawing.Point{78, 432}

SELF:olbChosen:Name := "olbChosen"

SELF:olbChosen:Size := System.Drawing.Size{100, 23}

SELF:olbChosen:TabIndex := 9

SELF:olbChosen:Text := "Chosen Settings :"

//

// ocbPreviewOnly

//

SELF:ocbPreviewOnly:Location := System.Drawing.Point{726, 408}

SELF:ocbPreviewOnly:Name := "ocbPreviewOnly"

SELF:ocbPreviewOnly:Size := System.Drawing.Size{104, 24}

SELF:ocbPreviewOnly:TabIndex := 10

SELF:ocbPreviewOnly:Text := "Preview Only :"

SELF:ocbPreviewOnly:UseVisualStyleBackColor := TRUE

//

// opbFindNDisplayDefault

//

SELF:opbFindNDisplayDefault:Location := System.Drawing.Point{12, 480}

SELF:opbFindNDisplayDefault:Name := "opbFindNDisplayDefault"

SELF:opbFindNDisplayDefault:Size := System.Drawing.Size{168, 23}

SELF:opbFindNDisplayDefault:TabIndex := 11

SELF:opbFindNDisplayDefault:Text := "Find / Display Default"

SELF:opbFindNDisplayDefault:UseVisualStyleBackColor := TRUE

//

// opbClearChosenSettings

//

SELF:opbClearChosenSettings:Location := System.Drawing.Point{12, 528}

SELF:opbClearChosenSettings:Name := "opbClearChosenSettings"

SELF:opbClearChosenSettings:Size := System.Drawing.Size{168, 23}

SELF:opbClearChosenSettings:TabIndex := 12

SELF:opbClearChosenSettings:Text := "Clear Chosen Settings"

SELF:opbClearChosenSettings:UseVisualStyleBackColor := TRUE

//

// ocbApplyUserSettings

//

SELF:ocbApplyUserSettings:Location := System.Drawing.Point{726, 336}

SELF:ocbApplyUserSettings:Name := "ocbApplyUserSettings"

SELF:ocbApplyUserSettings:Size := System.Drawing.Size{132, 24}

SELF:ocbApplyUserSettings:TabIndex := 13

SELF:ocbApplyUserSettings:Text := "Apply User Settings :"

SELF:ocbApplyUserSettings:UseVisualStyleBackColor := TRUE

//

// oReadBackOnDoPrint

//

SELF:oReadBackOnDoPrint:Location := System.Drawing.Point{726, 372}

SELF:oReadBackOnDoPrint:Name := "oReadBackOnDoPrint"

SELF:oReadBackOnDoPrint:Size := System.Drawing.Size{144, 24}

SELF:oReadBackOnDoPrint:TabIndex := 14

SELF:oReadBackOnDoPrint:Text := "Read Back on Do Print :"

SELF:oReadBackOnDoPrint:UseVisualStyleBackColor := TRUE

//

// opbDoTestPrint

//

SELF:opbDoTestPrint:ForeColor := System.Drawing.Color.Green

SELF:opbDoTestPrint:Location := System.Drawing.Point{726, 444}

SELF:opbDoTestPrint:Name := "opbDoTestPrint"

SELF:opbDoTestPrint:Size := System.Drawing.Size{114, 23}

SELF:opbDoTestPrint:TabIndex := 15

SELF:opbDoTestPrint:Text := "Do Test Print"

SELF:opbDoTestPrint:UseVisualStyleBackColor := TRUE

//

// opbCheckPCforA4

//

SELF:opbCheckPCforA4:ForeColor := System.Drawing.Color.Blue

SELF:opbCheckPCforA4:Location := System.Drawing.Point{726, 486}

SELF:opbCheckPCforA4:Name := "opbCheckPCforA4"

SELF:opbCheckPCforA4:Size := System.Drawing.Size{114, 23}

SELF:opbCheckPCforA4:TabIndex := 16

SELF:opbCheckPCforA4:Text := "Check PC for A4"

SELF:opbCheckPCforA4:UseVisualStyleBackColor := TRUE

//

// oPictureBox1

//

SELF:oPictureBox1:Image := ((System.Drawing.Image)(resources:GetObject("oPictureBox1.Image")))

SELF:oPictureBox1:Location := System.Drawing.Point{474, 336}

SELF:oPictureBox1:Name := "oPictureBox1"

SELF:oPictureBox1:Size := System.Drawing.Size{114, 150}

SELF:oPictureBox1:TabIndex := 17

SELF:oPictureBox1:TabStop := FALSE

//

// oPictureBox2

//

SELF:oPictureBox2:Image := ((System.Drawing.Image)(resources:GetObject("oPictureBox2.Image")))

SELF:oPictureBox2:Location := System.Drawing.Point{588, 336}

SELF:oPictureBox2:Name := "oPictureBox2"

SELF:oPictureBox2:Size := System.Drawing.Size{120, 150}

SELF:oPictureBox2:TabIndex := 18

SELF:oPictureBox2:TabStop := FALSE

//

// opbSaveUserSettings

//

SELF:opbSaveUserSettings:Location := System.Drawing.Point{540, 534}

SELF:opbSaveUserSettings:Name := "opbSaveUserSettings"

SELF:opbSaveUserSettings:Size := System.Drawing.Size{114, 23}

SELF:opbSaveUserSettings:TabIndex := 19

SELF:opbSaveUserSettings:Text := "Save User Settings"

SELF:opbSaveUserSettings:UseVisualStyleBackColor := TRUE

//

// opbViewUserSettings

//

SELF:opbViewUserSettings:Location := System.Drawing.Point{474, 498}

SELF:opbViewUserSettings:Name := "opbViewUserSettings"

SELF:opbViewUserSettings:Size := System.Drawing.Size{114, 23}

SELF:opbViewUserSettings:TabIndex := 20

SELF:opbViewUserSettings:Text := "View Stored Settings"

SELF:opbViewUserSettings:UseVisualStyleBackColor := TRUE

//

// opbApplyStoredSettings

//

SELF:opbApplyStoredSettings:Location := System.Drawing.Point{588, 498}

SELF:opbApplyStoredSettings:Name := "opbApplyStoredSettings"

SELF:opbApplyStoredSettings:Size := System.Drawing.Size{120, 23}

SELF:opbApplyStoredSettings:TabIndex := 21

SELF:opbApplyStoredSettings:Text := "Apply Stored Settings"

SELF:opbApplyStoredSettings:UseVisualStyleBackColor := TRUE

//

// opbExplain

//

SELF:opbExplain:ForeColor := System.Drawing.Color.Wheat

SELF:opbExplain:Location := System.Drawing.Point{378, 12}

SELF:opbExplain:Name := "opbExplain"

SELF:opbExplain:Size := System.Drawing.Size{90, 23}

SELF:opbExplain:TabIndex := 22

SELF:opbExplain:Text := "Explain"

SELF:opbExplain:UseVisualStyleBackColor := FALSE

//

// fmExaminePrintersTool

//

SELF:AutoScaleDimensions := System.Drawing.SizeF{((Single) 6), ((Single) 13)}

SELF:AutoScaleMode := System.Windows.Forms.AutoScaleMode.Font

SELF:ClientSize := System.Drawing.Size{882, 566}

SELF:Controls:Add(SELF:opbExplain)

SELF:Controls:Add(SELF:opbApplyStoredSettings)

SELF:Controls:Add(SELF:opbViewUserSettings)

SELF:Controls:Add(SELF:opbSaveUserSettings)

SELF:Controls:Add(SELF:oPictureBox2)

SELF:Controls:Add(SELF:oPictureBox1)

SELF:Controls:Add(SELF:opbCheckPCforA4)

SELF:Controls:Add(SELF:opbDoTestPrint)

SELF:Controls:Add(SELF:oReadBackOnDoPrint)

SELF:Controls:Add(SELF:ocbApplyUserSettings)

SELF:Controls:Add(SELF:opbClearChosenSettings)

SELF:Controls:Add(SELF:opbFindNDisplayDefault)

SELF:Controls:Add(SELF:ocbPreviewOnly)

SELF:Controls:Add(SELF:olbChosen)

SELF:Controls:Add(SELF:olbxChosenSettings)

SELF:Controls:Add(SELF:olbDefaultPrinter)

SELF:Controls:Add(SELF:olbDefaultPageSettings)

SELF:Controls:Add(SELF:ortbExaminePrinters)

SELF:Controls:Add(SELF:opbIsPrinterValid)

SELF:Controls:Add(SELF:ocbPrinters)

SELF:Controls:Add(SELF:olbWindowsPrinters)

SELF:Controls:Add(SELF:opbClose)

SELF:Controls:Add(SELF:ogbUserDefinedPageSettings)

SELF:Name := "fmExaminePrintersTool"

SELF:StartPosition := System.Windows.Forms.FormStartPosition.CenterScreen

SELF:Text := "Examine Printers Tool VS :"

SELF:ogbUserDefinedPageSettings:ResumeLayout(FALSE)

((System.ComponentModel.ISupportInitialize)(SELF:oPictureBox1)):EndInit()

((System.ComponentModel.ISupportInitialize)(SELF:oPictureBox2)):EndInit()

SELF:ResumeLayout(FALSE)

END CLASS

=======================================================================================

=======================================================================================================

PHH - 29th. March 2018.


Third Party report generators for .NET

One obvious problem with comparing Report Generators from third parties is that they are all complex in nature.

Some even more complex than others. None are simple!

The Frameworks which come as Third Party products are generally LARGE and complex, and the

documentation even bigger, or just HUGE.

*** NOTE *** One thing that now seems obvious to me, after many weeks of researching and work, is that

no Third Party company expects users and developers to hand code their own Reports or Documents.

All the Framework tools on offer seem to be biased towards the use of on-screen tools and Wizards, not

towards coding of any sort.

So how does a guy like me get to be able to compare one product with another ?

The answer is quite simple - "with great difficulty!".

However, I can still offer a few chosen words of advice, so don't go away, and keep reading this section.

Firstly, I have asked colleagues of mine on the XSharp Forum for any knowledge and guidance on .NET Reporting tools.

Secondly, I have done some useful web searching to include target words related to what I know as good providers of

GUI controls for both 'WinForms' and 'WPF'.

Thirdly, I bring with me some experience from the past of Reporting Services, and Crystal Reports.

Fourthly, I know a past customer / client of mine was advised by a large software / data support company

to start using 'Qlik', which I assume they still do.

So hopefully when I tap into all these approaches we will not miss out on finding a good possibility.

I suggest we will find out all those possible to us xBase guys.

Oh! - before we go forward a step or two, we do need to realise that often Third Party reporting products need Visual Studio

to be used by the report designer - and tester.

For this reason developers using XIDE will have great difficulty using modern Report Generators it would appear.

If you use XIDE and no WPF, as well as DBFs then it may be best to use my printing Class and DataTables of

the report data.

I suggest you think carefully of what you wish to do, what your limitations are, and where you may wish to go in the future.

If at all possible move to SQL data, WPF for GUI forms, which means Visual Studio for WPF and X# coding.

Although we in xBased X# can tap into all .NET tools, the third party companies out there do not consider DBFs and

IDEs other than VS. That is just a fact, albeit a hard fact.

The true also is that things are also moving in the WPF direction and WinForms stuff is 'falling off the end!'.

Go and have a DEEP think about this situation.

Lets move on now ............

=================================================================================================================

To find out about "DevExpress" try the following link :-

https://www.devexpress.com/Subscriptions/Reporting/

And here below is the start of the DevExpress information I have found :-

=======================================================================================================================================================

This next snippet suggests we are on the right track - note the WPF :-

================================================================================================================================

One good thing about DevExpress is that there seems to be easy access to documentation as a couple of images below will suggest

or show :-

==========================================================================================================================================================

And if we wish to drill down deeper, and see more about learning reporting with DevExpress, then we

need to move on to the child section from here named "DevExpress Reporting".

For now, lets move on to a company called "WinWard" :-

http://go.windward.net/BizReporting9.html?utm_medium=ppc&utm_source=bing&utm_campaign=googleweuro&utm_content=trial-dl-at&utm_term=%2Bc%23%20%2Breports

=================================================================================================================================================================================================

==============================================================================================================================================

https://www.syncfusion.com/products/report

====================================================================================================================================================================================================

======================================================================================================================================

[ .. the above may be from Syncfusion - check this out .. ]

Qlik :

https://www.qlik.com/us/products/qlik-sense

=====================================================================================================================

Crystal Reports :

http://www.c-sharpcorner.com/article/report-generation-in-net/

Stimulsoft Reports.Wpf :

https://www.stimulsoft.com/en/products/reports-wpf

PHH - 29th. December 2017.


a General approach to .NET Reporting ...

What are we trying to 'escape' from ? - what are we trying to change ? :

What on earth do I mean by this above ?

Well it is quite simple really .................

Over the years I have read many, many posts in the newsgroups and fora (many forums!) about RP2/3.

It would seem that we need to have a reporting solution which is quite separate form the versions of

VO/Vulcan and now XSharp. We need at least a pure .NET solution, and one that uses an IDE from a large

company like Microsoft to deliver the add-on / add-in tools..

We need to be at least "one layer" above what language/version the Reporting tools are written in.

We do NOT wish the tools to be linked in any way to the version of 'XSharp' that we are using to

create our apps.

We should NOT be limited in our choice of database that we use. But it must allow use of a range

of SQL products.

We should not be limited in the choice of GUI that we use from the .NET Framework. And it must

be WPF compliant / friendly.

There is no point in moving forward - BUT - to leave ourselves still in a 'hole'.

So we DO NOT select a good reporting product that does 'WinForms' and no other GUI. That would

simply restrict us to where Microsoft were ten years or more back.

If we get ourselves right bang up to date with the technology available, then at least we have bought

ourselves some time.

What is available ? - out there - and what is the cost ? :

There seems to be quite a few, indeed many, modern solutions to creating and printing Reports.

Some are targeted at .NET and most at SQL based data.

The best solutions seem to cost money, and at first it may seem like a lot of cash. However, it

would also seem that there are 'quality' software solutions out there that are of the highest quality.

Should we expect to get QUALITY software products for FREE, or little financial outlay ?

My personal view on this issue and Reporting is - keep well away from FREE tools, unless it is a

marketing strategy from a big firm like 'Syncfusion', who are allowing some companies (like ours?)

to use their highly priced software for no fee. That is NOT free, that is a marketing 'policy'.

In one sense Microsoft does exactly that with Community versions of Visual Studio and SQL Server.

They wish many people to be able to use their software products.

Be well aware that some solutions (even quality ones) are quite separate to our .NET applications,

and are Browser and network based. Some even don't seem to allow a choice of database product.

The 'Qlik' software seems to be in this category / 'ilk' - but you would need to cross-check that for

yourself, to make sure I am right in what I say.

The sort of Reporting software tools I would expect to use, are MOST likely to be available to us

from within Visual Studio. So use VS, SQL, and WPF as search terms when looking around.

How to select a suitable solution for YOUR circumstances :

What ever your choice of the reporting software 'package' you will need to put time aside to having

a real in-depth exploration of it and all its functionality and features.

Remember that quality software houses offer at least a 30 day trial period for developers to make

this in-depth assessment of their product. My advice is to use it wisely - get stuck in and give it

a good go. Just getting a few simple samples to work is NOT enough. You need to evaluate the

product in great detail. And 3o days is NOT a long time once the clock starts to tick.

It is always good to ask our respected colleagues what they do, or what they have heard. This is

exactly what I have done in the XSharp Forum. Consequently we now have a bunch of choices / options.

Nick Friend started telling us about 'DevExpress' (DX) and its products, many years ago. He still

uses them and he will contribute here, a few choices sentences and no doubt emotions too, when

he returns soon from Colombia.

How to start exploring your selected solution :

What is the 'best way' to take forward the new software for Reporting :

PHH - 2nd. January 2018.


Combining old and new reporting systems in one application

This is the 'Good News' part of this 'Printing and Reporting' section.

In our .NET applications there is no reason why we can mix our 'reporting systems'.

By this I mean have some reports from one third party company, and some from an entirely different

one, or even Microsoft's Reporting Services. And, even throw in one or two done by my 'BestDocument'

printing class.

This allows us to move forward without any need to rewrite what already works.

BUT, there is a possible restriction, and that comes at the IDE level.

When using Visual Studio to create X# apps which contain reports, we often design calling upon tools

from the Toolbox which have been installed from our chosen third party frameworks.

If we are not careful we can completely over-load the IDE with zillions of tools in our 'box'.

This is one reason why I suggest not rushing off and downloading all of the 30 day trial offerings from

the quality software houses.

In fact, even if you have longer than 30 days to spare for getting into reporting, I suggest you do your

homework carefully before hand, and DO NOT, install two large frameworks into VS together.

I have recently cleared my VS platform, and even re-installed VS 2015 altogether, so I could have just

the Microsoft tools and the DevExpress tool framework. This has made a big difference and my IDE

now runs much smoother and is more responsive.

Restricting the 'Third Party' install :

Even when you decide on a particular third party framework, ONLY chose the parts to install that you will

use, or are keen to explore.

By the above I mean don't select to install the WinForms parts of the framework if in fact you only are

interested to use the WPF related parts. Or vice versa, of course.

Try to keep your toolbox as simple as you can - BUT - be warned, you will still have VERY MANY tools

on hand to use.

Don't believe the Promise of Un-install ! :

And now my moan, or passing on of those "urban myths" !

Do not always believe that the un-install processes provided by others other than Microsoft, will actually remove

the tools from the VS environment.

I got in quite a mess by thinking that I could remove tools from my IDE toolbox - but in the end I had to do a clean

re-install of VS 2015. That worked when all else failed or only partly worked, at best.

AND, you can even have different versions of the same company's tools in the toolbox at once - even when you have

thought you removed / deleted the older ones the previous session.

So be aware - keeps things clean. And that means be careful what you do in the first place, it may not be

possible to easily undo what you rush into, when grabbing a bunch of nice tools.

PHH - 31st. December & 30th. January 2017.


Microsoft's Reporting Services ...

This is a topic of which I have quite a bit of personal experience.

I was paid by a customer of mine, for a good couple of years, to design and create reports in Visual Studio

using Reporting Services ('RS') - I used Visual Studio to design and test the reports.

The report files I created against a standard Microsoft SQL Server 2005, were then run in a web browser mode,

across his (my client's) internal network. So for me .NET was not involved at all - I learned much about SQL however.

I feel that for many X# guys this approach is NOT what they will be after. Myself, even I wish for a possible different approach.

[ .. I learned after writing this above and below, that RS can be used on in-Memory collections so look to later subsections .. ]

Although there is also a form control to show RS data / output, the system looks very (a bit?) old fashioned

when compared to some of the modern / recent reporting software tools. There are some REAL quality third party tools

on the market, and competition is fierce - which is a healthy and a good thing!

Both 'Reporting Services' and 'Crystal Reports' look and feel a bit 'old hat'. Or they did when I last looked at them.

However, RS is free to most or all of us, and so I will spend some time on it just to see what X# can gain by its use.

Personally, I feel that guys creating apps today need a nice GUI surface on which to slide around reporting elements.

We need to design our reports, not just get a list of numbers and strings out!

And talking of a nice interface for design, here is a simple example from Syncfusion :-

==================================================================================================

*** the tools from Syncfusion are free to small software houses - judged on annual turn-over. ***

Some thoughts on what needs answering :-

What is RS really ? and why did it arise in the first place ? :

How does 'RS' not really suit our .NET needs :

We need to do some work, and make some experiments as well.

PHH - 30th. December 2017.


Why I feel its NOT my solution for Reporting ...

We need to talk some simple logic in this section, I feel !

Below is a link to some commonly available MSDN information on using the WinForms control for displaying

an 'RS' (Reporting Services) report.

Yes, the first 'indicator' is that Microsoft do not provide a control for WPF ! That speaks 'volumes' !

And saying that it can be hosted on a WPF form within the WinForms 'hosting' control is not good enough either.

However I will show this working in a later section.

https://msdn.microsoft.com/en-us/library/hh273267.aspx

===========================================================================================================

Before we dismiss "out of hand", 'RS' as a possible solution to .NET reporting and printing, lets at

least look at a way that we can get some data displayed on a WPF surface of our forms.

Check this out below :-

=======================================================================================================================================================

===================================================================================================================================================

=====================================================================================================================

===============================================================================================

===================================================================================================================

Making some progress :

Having said what I did at the top of this section, here is an image of the 'ReportViewer' control hosted on a WPF window :-

[ .. remember the control is of the WinForms type .. ]

=====================================================================================================================================================

And the report does work as it should.

The section expand and collapse when the - / + icons are clicked.

So maybe later we should continue to explore the possibilities of Reporting Services.

PHH - 28th. January 2018.


Downloading the 'ReportViewer' control ...

In order to try the walk-through ourselves - that from the last sub-section - we need to find and download

the 'ReportViewer' control.

This is a GUI control we will place on the surface of our Form.

Here is a useful link I got to work for me yesterday :-

https://msdn.microsoft.com/en-us/library/ms251723.aspx

And you will see this - read it carefully :-

====================================================================================================================

Keep on reading - this is all very useful and informative stuff :-

[ .. I will show you images of my progress in doing this further below this part .. ]

=======================================================================================================================

The next part is for you knowledge and interest - you won't have to do anything :-

=====================================================================================================================

And now a bit for guys wanting more than a local app, or local network solution to reporting :-

=============================================================================================

And now we can start to get our hands dirty - and do something useful :-

====================================================================================

So after using the above web link we can start the download :-

=======================================================================================================

Here is how the top of the download page looks :-

======================================================================================================

And once the downloading begins we can see this :-

===============================================================

And with some luck we then get a new file in our 'downloads' folder as shown below :-

=================================================================================================================================

Then clicking to launch the RV exe file app we see this :-

======================================================================================

Yes, it looks a little dated (2010?) but it will still do the job required of it.

And now more images of success - see the following image :-

=====================================================================================

And that's it !

Before we leave this sub-section, and go to see how to use the Wizard to make a report, lets

take a moment or two to see how we integrate the new 'RV' (ReportViewer) control into our

solution.

We will need to add it to the References section - and it will look like this below :-

===============================================================

And to be able to add it as an assembly reference we need to first locate it in the

'Add References' dialog - not always easy !

=================================================================================

The reason I have shown the whole dialog is so you can see the 'thumb' of the scroll bar

to see for yourself (judge) the place down the list at which it is found - AND - the general

length of the list of items on offer.

There seems to be no sensible order of the items in the list - you just have to look very carefully.

I find this a little odd / weird - but then we can cope.

Oh! - we also need this shown below :-

=================================================

So what else needs to be added to an applet using the new RV control ?

What about the namespace section at the top of our PRG code files - see below :-

[ .. line 17 .. ]

==========================================================================

And we must not forget the XAML script - this needs an extra entry as seen on line 4 :-

====================================================================================================================================

Then in the above image of the XAML script, we need to view lines 11,12, and 13.

Notice that the RV control is of a 'WinForm' type so it has to be hosted on the WPF form.

This in itself tells us just where Microsoft place the RV technology in the greater scheme of things !

Or that's how I see it.

Lets move on ...........

In the next section we will see how to get the Wizard to help us guys make a working report in C#.

The reason we use C# is so that once we have everything working we can then copy things over, in such a way

as to get an X# version working as best we can.

"One step at a time ..." a good way to work I feel ;-0)

See you there ................

PHH - 28th. & 30th. January 2018.


'RDLC' files - meaning ....

Well all we need do to answer the question of what does RDLC stand for, or mean, is to

Google it :-

========================================================================================================================

PHH - 2nd. February 2018.


Getting the MS sample to work in C# ...

Here, in this subsection we will see how to make the Microsoft sample work for us in C#.

We will then adapt the C# code to get it to work with a ClasLibrary, since this is a possible

way forward for use X# guys to be able to use the C# template Wizard.

Using Wizards is a real handy thing to do.

Lets see the SE pane of the working C# sample, for now ignore the collapsed 'ClassLibrary' folder :-

=====================================================

Notice at the bottom of the SE pane content, we see two report entries.

These are what is made when we use the 'Wizard'.

Before we focus on the Wizard and it's use, lets see some suitable data - similar to that which the

MSDN sample uses - this was a free download many moons ago :-

===========================================================================================================================================================

The same .MDF should be available somewhere on the net - it was free.

So now we have our suitable data for use with the Wizard - what is the Wizard, and where do we find it ?

Highlight the project folder in the SE pane, and then right click it to get the contents menu.

Select 'Add' and then 'New Item', and navigate the folder tree structure in the LHS pane of the dialog.

You should be able to see the 'Report Wizard', which we will call just 'Wizard' from now on :-

=====================================================================================================================

The highlighted template shown above is what we need to use.

I call it a template, simply because everything else I saw, selected, and used from the 'Add New Item' dialog was a

template - will need to check this with Fabrice.

Before you select the Wizard, make sure you have the correct SE pane folder in focus.

For a trial we can highlight the project folder - here called 'WpfCSapp'.

So when we select and use the Wizard we see the following :-

================================================================================================================

And yes, we want a Database (for now).

Then we get the following dialog :-

================================================================================================================

After selecting 'Next' we see the following :-

==================================================================================================================

Above, I have expanded the connection string section so we can see the details.

But the one shown below may be the one for the data and report which follows :-

=================================================================================================================

Don't panic, you will get better and better at working your way through the wizard dialogs.

And now to save the string :-

===============================================================================================================

And so we get to selecting details read from the database :-

========================================================

For most straight forward reports we need tables, or Views if there is any company security etc.

Views are just like readable Tables - just another security layer really.

Here is what is on offer :-

============================================================

So we need to select a few which can give us a meaningful report, as such.

Lets choose the Sales 'stuff' :-

========================================================

Okay folks, we now return to our Report Wizard screen / dialog :-

=============================================================================================================================================

If you so wish you can change the name of the Dataset but make sure you make a note of it

for later use in the coding.

Lets now do what the walk-through suggests, and select, copy, and paste all the fields :-

=========================================================

This action from above, produces the following :-

========================================================================

And the next step is even starting to look like a report :-

======================================================================================================================================================================

But we may wish to change the colour to Forest Green :-

===========================================================================================================================================================================================

And then its all done - Finished !

The SE pane now has extra entries made by the wizard template - check below :-

=======================================================

=================================================

Four lines of Dataset have been added after the References section.

The Report entry is '2' simply because there is a '1' version which has been moved.

More on the 'move' later, and why we have done it this way.

Lets now have some 'Fun' ! :

Lets run the sample to see what happens.

The sample being run is that above, but with the code lines added which were suggested

in the MSDN example.

WE CAN DISCUSS THE CODE AND SCRIPT LATER :-

First the action ..............

========================================

Obviously, when we get the timing cursor and a loading message, the SQL database is being read.

Then this below springs into life - yes its a report :-

==================================================================================================

Yes, its very easy to get excited about a ugly looking report - simply because it is so difficult to get

all this stuff to work in the first place ;-0)

There are so, so many places it can go wrong !

So what do we need do to make it all work ?

Lets start with the XAML script of the WPF form :-

===============================================================================================================================

And now some C# code (sorry to the haters!) :-

=================================================================================================================================================================================

Check the lines above which refer to the data set.

Here is a clearer sample to read :-

============================================================================================================================================

We may need to come back to refer to this later.

Fo now lets move forward and see some more useful stuff ....

PHH - 28th. January 2018.


Navigation: 'Printing and Printers', and Report generation too ... > Third Party report generators for .NET > Microsoft's Reporting Services ... > Why I feel its NOT my solution for Reporting ... > Getting the MS sample to work in C# ... >

Return to chapter overview

Making a C# Class Library ...

Lets get a few things clear from the very start, just so we know where we are going.

We are going to explore the ClassLibrary object and what it can do for us, simply because we

will have template tools available to C# projects / solution when we need to use them with code

in other projects which are X# in nature.

One beauty of the .NET framework is that we can 'mix' different .NET languages as long as we

keep the same language through-out one project.

So a 'ClassLibrary' written to work with a C# project in one solution, can be used with a different

project written in X# in a different solution.

Obviously, the C# ClassLibrary works with a C# project - as seen as our starting point below :-

=================================================

If we are to use a ClassLibrary in our main project, then it has to be added to the References

section of the SE pane content - see below :-

================================================

Oh! and by the way, to make things clear, the name 'ClassLibrary1' is just a default name provided

by the template to make the library.

It could have just as easily be named (by us) as 'OurLibrary13', or any other suitable name. Check

this out below :-

============================================================================================================================================

And the result of our recently made class library is as seen in the following image :-

==============================================

Now then folks, its all right making an extra (and new) library but we do need to 'point' the main

project at it - via the References section - study some details in the next couple of images :-

===================================================================================================================================

To the existing 'checked' entry we need to check the latest one - as seen below :-

====================================================================================================================================

After clicking OK we should see something like the following :-

================================================

Okay guys, its time to move on ..........

*** WARNING *** Just as a thought before we rush off and learn something new / more, we will need

to refer later to how to best use a Class Library from outside of the current solution.

When we use DLLs which are from other solutions we need to make sure that the 'versions' match up.

By this I mean the version of the latest successful compilation of the <CL>.dll and the assembly which is

added into References section. These need to be the same.

Check out this aspect of 'ClassLibraries' later in these eNotes.

Lets move on a pace or two .........

Exploring how the Wizard places items in the 'Main' and 'ClassLibrary' project folders :

To use the Report Wizard in the C# template dialog, we first need to highlight the project we intend

the new items to populate.

In the following image we highlight the 'WpfCSapp' folder and then Add, New Item ....

After following through the Wizard as we saw in the parent folder, there are a bunch of new line entries.

In the following image we get a new Report entry, and it is named 'Report3.rdlc'.

We also either get a new 'set' of DataSet entries, or we have chosen to use the existing ones when we

followed through the Wizard queries.

======================================================

So what happens in the ClassLibrary if we highlight it before calling up the wizard.

Well, we can try with our newly made class library 'OurLibrary13' to see what we get,

or what happens.

Here are the results :-

[ .. SORRY ! - I made a spelling mistake of 'Libary' instead of 'Library' and renaming does not work .. ]

=======================================================================

Yep! - we have both a newly define DataSet group of entries, as well as our newly made (and aptly named)

report file "ReportOurs13.rdlc".

This seems all very well until we need to add code into our main project module.

Lets see what I can do to modify and adapt the C# code to display within the 'ReportViewer' control.

Before we forget lets add the namespaces we may require :-

=====================================================

And with a bit more effort we can adapt and change the C# code in the MSDN sample to look like this :-

=========================================================================================================================================================================

Okay guys, we can see that it does not compile and run correctly - the compilation seems okay,

but the system does not seem to be able to find the definition for the Report.

This would seem to me that line 53 is incorrect in this respect.

So what if we copy the project entry for 'ReportOurs13.rdlc" into the main project and change the

code accordingly ?

=========================================

For the moment, don't worry about the other settings in the code, for DataSets and the likes, these we can checkout later.

Lets get the sample to run first :-

===============================================================================================================================================================

Well, this one small change has made the sample run, and give the simple listed report of the Registrations.

Okay, I know, the report does not look very pretty but it does work.

So as a way of getting the Report Wizard to work for us in an X# app., this may well work OK.

We shall see in another sub-section - later.

In the above code notice that line 51 now refers to the 'EmbeddedResource' in the main project, on line 51.

Lets look at the last few lines of this display method :-

=======================================================================================================================================================

Although this works, it does feel a lot more 'Old Fashioned' and very 'codey' compared to the modern DevExpress type of approach.

More of this code needs to be hidden away, AND, not by us the developer I feel.

The other thing I noticed with the report when displayed, it did not have reactive column headers, so

we could not click in the header and change the order of the data lines.

We have now come to expect such sorting behaviour in display grid type Form controls.

Moving on ..............

Finally, in case you are interested, we can return to the original MSDN sample and see how

I have made further reports than that in the sample, as well as move as many parts as possible

into the class library :-

=============================================

Any and all of the DataSets can be placed in the class library, but the '.rdlc' entries need

to be in the main calling module it would seem.

Checkout the code which runs the first report (as in the sample), 'Report1.rdlc' :-

===============================================================================================================================================================

Okay folks, time to move on ..............

See you in the next sub-section where we can look at the ReportViewer class and its members.

We need a break from C# and ClassLibraries etc., etc..

PHH - 29th. & 31st. January 2018.


Navigation: 'Printing and Printers', and Report generation too ... > Third Party report generators for .NET > Microsoft's Reporting Services ... > Why I feel its NOT my solution for Reporting ... > Getting the MS sample to work in C# ... >

Return to chapter overview

Changing the returned data 'SORT' order - easily ...

When we use the Report Wizard from the C# templates, we end up with some DataSet

entries in the project that was highlighted at the time of calling up the template.

Below is an example of such entries :-

======================================================================

Lets now explore some possibilities of what can be done from the SE pane, and

subsequent dialog windows.

If we double click the '.sxd' item we see the following, or similar :-

=======================================================

Yes, okay, we expect to see the named fields, BUT, if we click within the 'Fill, GetData' item

we see in the Properties pane the following :-

=======================================================================

Note the ellipses at the RHS of the Command line.

A fuller view of the Fill Query is seen below :-

=======================================================

Moving on ...........

If I click the ellipses I see an interesting screen which is all about managing the SQL query

used by the DataSet to retrieve the return data.

Checkout fully and carefully that shown in the next image :-

===============================================================================================================

Now then guys, anyone even a bit familiar sees a SELECT statement and therefore its a good guess that we

can manually change this if we have some basic knowledge of T-SQL script.

Lets try and make our data come out in a specified order, and only for people with a last name greater than 'F'.

Here is my modified query statement :-

=========================================================================================================

Note that the 'Execute Query' button lets us check we are getting what we wished for.

Note also I have made the select statement provide 'Lastname' as the first field in the returned lines

of data - more on that later.

Now see what we get when we click 'OK' :-

===========================================================================

So we can choose to discard our editing if we so wish, but often I suspect we may

well keep it.

Lets run the app afresh and see what we get in our report viewer :-

=========================================

Note that in the above image the Lastname entries go from 'T' to 'F' ...

And are in descending order, as we say, Z to A.

Now then, should we expect the report to place the 'Lastname' first, at the start of each line ?

Lets see what we get at runtime :-

=======================================================================================================================================================

Can you guess what is happening ?

If we click the 'ReportOurs13.rdlc' entry we see the following :-

=======================================================================================================================================================

It looks very much that the report definition is overlaying a column order to the returned data.

So the 'Lastname' field comes where the report definition says it will ! As indeed you may expect.

So what if we locate the correct report definition and select it with a click or two - here is the one we need :-

=======================================

The clicking in the SE pane opens up the report designer as seen below :-

==========================================================================================================================================================================

However, you can see that I have been messing around with the editing and design tools.

I have inserted a column at the start of the definition - LHS using this :-

=======================================================================

Into this new column I have placed two text boxes and filled them appropriately.

One with the text of 'Lastname' the other by associating it with a data field (binding?).

=============================

We will format these better in a short while.

For now notice we how to make the association - do the binding :-

=================================================

From the above list we need to select 'Lastname'.

Now move along the designer contents and select in the column for Lastname.

Right click for the context menu and then select 'Delete' - job done!

========================================================================

Lets see how we go about formatting a TextBox object.

We need to copy/provide the same 'Fill Color' to our new 'Lastname' text box - see the value required :-

========================================================================================================================

So all that is left for us to do is compile and run the app code to see that the Lastname column now comes first.

Summary - it is the report designer and the RDL file which specifies the column order - NOT - the returned data set.

I would like to have shown you this - BUT - I recently had a NASTY experience with a Windows 10 auto update ;-0((

I have a new / replacement boot C: disk and am much less than 50% of being up and running, as I was.

We will have to manage ;-0)

PHH - 31st. January 2018.


ReportViewer class ...

https://msdn.microsoft.com/en-us/library/microsoft.reporting.winforms.reportviewer.aspx

========================================================================================

This detail shown in the current sub-section is useful for us to know etc. ..................

===================================================================================================================

=========================================================================================================================

PHH - 28th. January 2018.


Printing a Report ...

Okay then guys, once we have been able to define and create a report using RS, we can easily print

it off using the menu bar functions of the Report Viewer control.

See below the 'printer' icon :-

==================================================================================================

I will leave it to you to have a play around and experiment with the facility.

PHH - 28th. January 2018.


Getting the MS sample to work in X# ...

Okay guys, like all good things in life we get better results if we look to some suitable 'Tips and Tricks'.

I think in the old days we used to call this 'experience' !

Whatever your choice of phrase I have some to share with you ;-0)

What do we need to do - and Why ? :

Lets work backwards, with what we need to do / achieve.

In order to display a Report on the Microsoft 'ReportViewer' control, we need an object of Type 'RDLC' to

be available in our SE pane. And this needs to be set as an 'Embedded Resource' as well.

In the image shown below we can see I have highlighted five of these such objects :-

================================================

So how do we make these, and how do we get them into the SE pane, within the X#

project in which we are developing ?

Well, we need to run the template Wizard - it is shown below as "Report Wizard" :-

=============================================================================

So what is the Problem / Issue ?

Well, this template is only on offer within a C# project, and ours is an X# one.

So "Trick One" is to first make a new Project within our solution, and make this new

project a C# 'ClassLibrary'.

Look at the following image to see that I have a basic ClassLibrary (C#) project, and that I

also seem to have already created five '.RDLC' files (report definition language client-side) :-

======================================

It is easy to make these .rdlc files as we saw previously, a few sub-folders back.

The problem is that we can't access them, it would seem, directly from the X# project.

All we need to do is copy (drag and drop/copy) the RDLC files, once made, into the X# project from

the ClassLibrary.

Make sure you remember to set the 'BuildAction' property as shown below :-

======================================================

Then code in the X# project can access them like the following image shows :-

================================================================================================================================

Line 37 in the above code is how we reference (or get to) the RDLC files - here we are just accessing 'Report5'.

Don't think that because the file/object is within the same project that we can shorten the string, it does NOT

work as the next image shows :-

=============================================================================================================================

Okay then folks, lets move on a few steps, to see what needs doing if we are using In-Memory objects.

Study the follow section .................

What else do we need to do ? :

If we are to use an 'In-Memory' object as our data source, then we will provide this within the X# project.

This means that when we add a new item from within the C# ClassLibrary project, we need that project to

be able to reference the X# project.

The simple answer is to at a reference in the References section of the class library - as seen below :-

============================================

*** CARE *** do not also place a reference in the X# project to the C# class library project, or you will get

an error box warning of a cyclical reference - which is NOT allowable.

Makes sense when you think about it.

Here is the X# class definition required by the Wizard from the class library :-

=====================================================================================================

And although the Orders.GetOrders() STATIC method is required for making the RDLC, we also need to

call the same method when displaying the details in the RV control - see below :-

========================================================================================================================

And just to prove that all my messing about has not broken things, here is a compile and run, with resultant display of the report :-

=======================================================================================================================================================

Not bad, maybe we need a couple of summations under the four Cost columns.

Time to move on however, we need to learn some more 'stuff' on reporting ;-0)

See you in the next sub-section ................

PHH - 29th. January 2018.


Navigation: 'Printing and Printers', and Report generation too ... > Third Party report generators for .NET > Microsoft's Reporting Services ... > Why I feel its NOT my solution for Reporting ... > Getting the MS sample to work in X# ... >

Return to chapter overview

Consuming a C# Class Library from X#...

=======================================================================================

using System

using System.Collections.Generic

using System.Text

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.Navigation

using System.Windows.Shapes

//------------------------------------

using Microsoft.Reporting.WinForms

begin namespace WpfDXgoGO

  public partial class WPF_XS_RSinMemObj2 INHERIT Window

      public CONSTRUCTOR() //WPF_XS_RSinMemObj2()

           InitializeComponent()

           _reportViewer.Load += ReportViewer_Load

RETURN

      private _isReportViewerLoaded as logic

      private method ReportViewer_Load(sender as object, e as EventArgs) as void

          if (!_isReportViewerLoaded)

              var reportDataSource1 := ReportDataSource{}

               reportDataSource1.Name := "DataSet1" //---Name of the report dataset in our .RDLC file

              //reportDataSource1.Value := person.GetPersons()

              //reportDataSource1.Value := person.GetPersonsAddressHas9()

               reportDataSource1.Value := Order.GetOrders()

              //---------------------------------------------------------------

              self._reportViewer.LocalReport.DataSources.Add(reportDataSource1)

              self._reportViewer.LocalReport.ReportEmbeddedResource := "WpfDXgoGO.Report5.rdlc"

              //self._reportViewer.LocalReport.ReportEmbeddedResource := "Report5.rdlc"

               _reportViewer.RefreshReport()

               _isReportViewerLoaded := true

          endif

      return

  end class

End NameSpace

=======================================================================================

<Window x:Class="WpfDXgoGO.WPF_XS_RSinMemObj2"

      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

      xmlns:rv="clr-namespace:Microsoft.Reporting.WinForms;assembly=Microsoft.ReportViewer.WinForms"

      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"

      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"

      mc:Ignorable="d"

      Title="WPF_RS_objTest" Height="660" Width="825">

  <Grid>

      <WindowsFormsHost>

          <rv:ReportViewer x:Name="_reportViewer" AutoSize="True"/>

      </WindowsFormsHost>

  </Grid>

</Window>

=======================================================================================

<Application x:Class="WpfDXgoGO.App"

  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

  StartupUri="WPF_XS_RSinMemObj2.xaml">

  <!--StartupUri="WPFWindow1.xaml"-->

  <Application.Resources>

  </Application.Resources>

</Application>

=======================================================================================

=======================================================================================

PHH - 29th. January 2018.


Reports based on 'In-Memory' objects ....

Okay guys, love it or hate it, we need quite often to start with a working sample of C# code.

Here the task is suitable for us to start with C# and work towards a useful X# approach.

I have taken some time today to get my example to produce a small data results table.

But now I have I should be able to get something working okay in X#.

Here is what we need on which to build /base our next steps :-

==============================================================================================================================================================================

This is a basic start, up till now I got nothing !

We will have to work on how to get Gender to display and print properly (its an enum) but more of that later.

Here is another useful and related image - good when we need to check stuff out :-

[ .. notice that this is the VS view when we open a file in the .rdlc Designer .. ]

======================================================================================================

Lets move on ............

PHH - 2nd. February 2018.


Navigation: 'Printing and Printers', and Report generation too ... > Third Party report generators for .NET > Microsoft's Reporting Services ... > Why I feel its NOT my solution for Reporting ... > Reports based on 'In-Memory' objects .... >

Return to chapter overview

working script and C# code

==========================================================================================================

using System;

using System.Windows;

//------------------------------------

using Microsoft.Reporting.WinForms;

namespace WpfCSenumTest_01

{

  public partial class WPF_RS_objTest : Window

   {

      public WPF_RS_objTest()

       {

           InitializeComponent();

           _reportViewer.Load += ReportViewer_Load;

       }

      private bool _isReportViewerLoaded;

      private void ReportViewer_Load(object sender, EventArgs e)

       {

          if (!_isReportViewerLoaded)

           {

              ReportDataSource reportDataSource1 = new ReportDataSource();

               reportDataSource1.Name = "DataSet1"; //Name of the report dataset in our .RDLC file

               reportDataSource1.Value = person.GetPersons();

              //---------------------------------------------------------------

              this._reportViewer.LocalReport.DataSources.Add(reportDataSource1);

              this._reportViewer.LocalReport.ReportEmbeddedResource = "WpfCSenumTest_01.Report2.rdlc";

               _reportViewer.RefreshReport();

               _isReportViewerLoaded = true;

           }

       }

   }

}

==========================================================================================================

<Window x:Class="WpfCSenumTest_01.WPF_RS_objTest"

      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

      xmlns:rv="clr-namespace:Microsoft.Reporting.WinForms;assembly=Microsoft.ReportViewer.WinForms"

      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"

      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"

      xmlns:local="clr-namespace:WpfCSenumTest_01"

      mc:Ignorable="d"

      Title="WPF_RS_objTest" Height="660" Width="825">

  <Grid>

      <WindowsFormsHost>

          <rv:ReportViewer x:Name="_reportViewer" AutoSize="True"/>

      </WindowsFormsHost>

  </Grid>

</Window>

==========================================================================================================

=============================================================================================================

=============================================================

using System.Collections.Generic;

using System.Windows;

//====================================

namespace WpfCSenumTest_01

{

  /// <summary>

  /// Interaction logic for MainWindow.xaml

  /// </summary>

  public partial class MainWindow : Window

   {

      public MainWindow()

       {

           InitializeComponent();

       }

   }

  public enum PersonGender

   {

       Female = 1,

       Male = 2

   }

  public class person

   {

      public string Firstname { get; set; }

      public string Lastname { get; set; }

      public string City { get; set; }

      public PersonGender Gender { get; set; }

      public person()

       {

       }

      public person(string cF, string cL, string cC, PersonGender pgG)

       {

          this.Firstname = cF;

          this.Lastname = cL;

          this.City = cC;

          this.Gender = pgG;

       }

      public person(string cF, string cL, string cC)

       {

          this.Firstname = cF;

          this.Lastname = cL;

          this.City = cC;

       }

      public static List<person> GetPersons()

       {

          List<person> aList = new List<person>();

           aList.Add(new person("Annabel","Jones","Durham",PersonGender.Female));

           aList.Add(new person("James","Rydel","Manchester",PersonGender.Male));

           aList.Add(new person("Sally","Spacegirl","Bristol",PersonGender.Female));

           aList.Add(new person("Helen","Holms","Durham",PersonGender.Female));

           aList.Add(new person("Jon","Boye","Manchester",PersonGender.Male));

           aList.Add(new person("Sara","Willington","Bristol",PersonGender.Female));

           aList.Add(new person("Alison","Williams","Doncaster",PersonGender.Female));

           aList.Add(new person("Granvil","Grumpey","Liverpool",PersonGender.Male));

           aList.Add(new person("Liz","Longish","Bristol",PersonGender.Female));

          return aList;

       }

   }

}

==============================================================================

PHH - 2nd. February 2018.


X# code syntax for 'In-Memory' object reports ...

Well guys, the news is good - we have 'lift-off' !!!

I have a working test app in X# which uses Report.rdlc files made by the Wizard available in C#.

It is easy and works well - AND - has some useful flexibility in its possible application too.

A piece of success :

Okay then, lets start by looking at a PDF which was printed from a report made on an in-memory

object in our X# code.

Printing to a PDF is just like printing to a printer - we go through a printer driver bit of software.

We can work through what needs doing after enjoying the view of our success - check below :-

======================================================================================================================================================

We have only three lines of 'Person' data because we have applied a filter using a LINQ wrapper, around the full data set.

Here is the wrapper code :-

=====================================================================================

In the above image the 'where' clause does the filtering.

The basic / original 'Person' data set is as follows :-

================================================================================================================================

When we use and apply the C# 'Report Wizard' we make the Report.rdlc file within the 'ClassLibrary1' project.

In order for the Wizard to have access to (be able to see) the STATIC data list, we must provide a suitable

reference in the References section of the ClassLibrary.

*** Remember *** we are to run the Wizard while the ClassLibrary project is in focus.

This is what was required for my solution :-

====================================================================

And before we get into what is required at the IDE surface, to make the Wizard work, lets see the X# code

which is the successful code and which makes a populated report window - check below :-

=======================================================================================================================

Yes, I know this is a bit old fashioned in how it works ;-0(

We need to know that the Wizard made a DataSet called 'DataSet1' and that the report is 'Report2' in the project

named 'WpfDXgoGo'.

More modern tools would hide this 'ugly' side of things, and do it instead by GUI view and select / click etc.. As well

as taking control of the whole process - managing it.

Okay then guys, lets see the RDLC Design surface, as it is slightly overlaid with the runtime results window :-

===================================================================================================================================

Remember that this detailed report is built, or based on, a .NET object - actually a Collection of business class objects.

The List<T> used is actually a List<Person> object - where 'Person' is a simple business class I defined.

Lets get to using the Wizard :

In the SE pane highlight the C# ClassLibrary folder as seen below :-

===============================================

Right click to bring up the context menu and then 'Add' >> 'New Item' >>

Then you should see the template selection dialog for C# - simply because we are adding an item to an existing

C# project - this is what we see :-

=====================================================================================================================

Choose the Wizard, so that we can make choices more easily than when NOT using it.

Here is what we see :-

================================================================================

If we have 'linked' the ClassLibrary correctly with the X# project, by adding the correct

reference into the References section, then we should see the correct Data Source option(s).

And now we see the possible (available) datasets :-

========================================================================

Because we have such a simple sample app we only get one choice :-0)

So we will have 'Person', I think. ( "You can have any colour you like as long as its black". - Henry Ford. )

What do we see if we select 'New' ? :

Lets choose the 'New' option button in the Wizard screen / window shown above, this below

is what we then see :-

===============================================================================

Here we are interested in the 'Object' choice for this eNote section - so what does that bring up ? :-

=========================================================================================================

Yes, in the above image we see that 'Person' is the only real sensible choice for us. If we had had others,

like Customer, Order, Product, then these would also have shown up.

Moving on ...........

Selecting 'Person' brings up the field names and their data types :-

===================================================================================================================================================

Yep! the fields look right / correct, so lets add some to the 'Values' box :-

==================================================================================================================================================

Finally, we get to the style and layout dialogs, first that for 'Layout' :-

================================================================================================================================================

And now Style - lets change the colour :-

=============================================================================================================================================

And when the Final button is clicked we get the 'RDLC' file Designer layout as seen below :-

==========================================================================================================================

Although the designer layout shown above looks simple and unsophisticated, it does have a LOT of hidden functionality.

The best thing is to learn bits as we go along, addressing one small issue after another, then we grow towards experience!

Lets study the 'raw' output and see if we can improve anything - check this out below :-

===================================================================================================================

Well, we can see a few small things that need to be improved.

First lets 'ease' some of the column 'boundaries'. Address needs to be wider for example. And 'Gender' is too wide.

If we click within the design layout we see an extra grey border appear to the left and top of the cells. See below for this :-

================================================================================================================================

If we pick up (with the mouse pointer) and move the vertical column dividers we can move the boundaries as we wish to see them.

The image above shows the results of me having moved a couple to suit the improved look / layout. Age has also been narrowed a little.

Now in the next image we have selected the DoB data cell (one under the header) and have used the context menu to bring up the

Expression dialog window :-

=================================================================================================================================

Although very old fashioned in its looks (and operation I feel) there is actually a LOT of functionality built in to the

various sub-areas - see below :-

=============================================================================================================

This is one example where we need to learn parts by tackling simple issues and problems as they arise.

Below we are formatting the 'Date' output / or display.

======================================================================================

We can actually copy and paste text from the 'Example' pane at bottom right - I pasted it directly into the

'set expression for:' text pane.

When we get the text input syntactically correct, the red wavy line disappears - quite handy!

So what does our tidied report format look like now ? - check below to see :-

============================================================================================================================

Okay, I know, we have a possibly more difficult issue to put right, we need the Gender items to be displayed as 'male' and 'female'

in place of the 1's and 2's.

This could prove to be more problematic than it first looks - may need to talk with Robert on this one.

Moving forward - Lets get a bit of Grouping done :

In the 'Arrange Fields' dialog window (quite a few back up the page) we had a 'Row groups' pane which we left blank / empty.

Lets go back and slide (drag / drop) the 'City' field into the receiving pane as shown below (after sliding) :-

==================================================

Now when we step forward through the Wizard we see a different structure to the Report Designer surface.

Below we see we have four lines and not just two :-

================================================================================================================================================

Lets get some colour going and select Mahogany to get this nice combination of colours :-

=====================================================================================================================================================================

The resultant report display looks like this in the following image :-

===================================================================================================================================

Note that when first displayed the sections are collapsed (this is the correct term for closed).

If I expand the two sections by clicking in the +/- icon box we see this shown below :-

==============================================================================================================================

Now then, because I have remade the report definition from scratch (the start), we have lost the formatting we did earlier in

this section of the eNotes.

I will go and re-do the formatting, as well as easing the first columns to allow the City name to be all on one line.

In order to get sufficient line entries to make the City grouping worthwhile to see, I have changed the data source to

that seen below on line 32 :-

==================================================================================================================================

Here we see the date being formatted with a click in the data cell for DoB :-

=============================

And finally, the report is starting to look at least 'half-decent'.

Study the full layout and its details :-

=====================================================================================================================================================

Okay guys, time to move on ..................

Hope you have enjoyed learning some of the above 'stuff'.

See you in the next sub-section ....

PHH - 2nd. & 3rd. February 2018.


Navigation: 'Printing and Printers', and Report generation too ... > Third Party report generators for .NET > Microsoft's Reporting Services ... > Why I feel its NOT my solution for Reporting ... > X# code syntax for 'In-Memory' object reports ... >

Return to chapter overview

working script and code ...

=========================================================================================

using System

using System.Collections.Generic

using System.Text

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.Navigation

using System.Windows.Shapes

//------------------------------------

using Microsoft.Reporting.WinForms

begin namespace WpfDXgoGO

  public partial class WPF_XS_RSinMemObj2 INHERIT Window

      public CONSTRUCTOR() //WPF_XS_RSinMemObj2()

           InitializeComponent()

           _reportViewer.Load += ReportViewer_Load

RETURN

      private _isReportViewerLoaded as logic

      private method ReportViewer_Load(sender as object, e as EventArgs) as void

          if (!_isReportViewerLoaded)

              var reportDataSource1 := ReportDataSource{}

               reportDataSource1.Name := "DataSet1" //---Name of the report dataset in our .RDLC file

              //reportDataSource1.Value := person.GetPersons()

               reportDataSource1.Value := person.GetPersonsAddressHas9()

              //---------------------------------------------------------------

              self._reportViewer.LocalReport.DataSources.Add(reportDataSource1)

              self._reportViewer.LocalReport.ReportEmbeddedResource := "WpfDXgoGO.Report2.rdlc"

               _reportViewer.RefreshReport()

               _isReportViewerLoaded := true

          endif

      return

  end class

End NameSpace

=========================================================================================

<Window x:Class="WpfDXgoGO.WPF_XS_RSinMemObj2"

      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

      xmlns:rv="clr-namespace:Microsoft.Reporting.WinForms;assembly=Microsoft.ReportViewer.WinForms"

      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"

      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"

      mc:Ignorable="d"

      Title="WPF_RS_objTest" Height="660" Width="825">

  <Grid>

      <WindowsFormsHost>

          <rv:ReportViewer x:Name="_reportViewer" AutoSize="True"/>

      </WindowsFormsHost>

  </Grid>

</Window>

=========================================================================================

==========================================================================================

========================================================

==========================================================================================

<Application x:Class="WpfDXgoGO.App"

  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

  StartupUri="WPF_XS_RSinMemObj2.xaml">

  <!--StartupUri="WPFWindow1.xaml"-->

  <Application.Resources>

  </Application.Resources>

</Application>

==========================================================================================

PHH - 3rd February 2018.


Using the Expression dialog window ...

Okay guys, we all need to take on board the fact that in order to get the best out of RS (Reporting Services)

we need to be proficient (quite good) at using the 'Expression' dialog screen / window.

In the context menu for on screen items like 'Text Box' we see the option available - as seen below :-

============================================

When we choose to click 'Expression' we get something similar to that shown below :-

=========================================

Note that it already show some content from previous screen operations.

From the open dialog we can edit and change this content - in many ways.

Before we go into any real detail we need to have a look at what options are available.

Remember the dialog screens we bring up are designed to help us locate and select meaningful options.

=====================================================================================================================

As seen above, we have some system variables on hand for us to select, like page number.

Obviously, we would place 'PageNamuber' in a text box that is within the footer area of the banded report.

'ReportName' would go in the header area.

Lets view some more that are available :-

=================================================================================================

Arrh! - we can see just which data fields are available to us.

Usually (always?), we would be selecting these for text box items which are in the body areas of the banded report.

In the right areas we can select and use aggregate data, like sum and max, min, first etc. We need to practise I feel ;-0)

======================================================================================================================

Checkout the possible operators - do this for yourself, and check more than just Logical.

======================================================================================================================

Below we see a bunch of common functions available to us, and we are seeing just the 'Math' ones :-

=====================================================================================================================

It is rather important that we get good at string related functions, and know where and how to find them - check below :-

======================================================================================================================

This next one may interest you - not used these a lot as yet, BUT they are important, see example :-

=======================================================================================================================

Now check what the 'Switch' example has to tell us :-

================================================================================

Okay then folks, let move on, but remember you need to get yourself familiar with all these options.

This is what makes your reports look good, and professional.

Lets look at my on-screen example - see below, and check out the date column :-

========================================================================================================

When we select 'Expression' on a date/time related column we see this is possible :-

====================================================================================

Have a play around with options for dates and times etc.

Now lets see what can be done with summation.

==========================================================================================================================================================

As you can seethe column has an expression '<<Expr>> ' indicator content.

Lets see what this looks like in the Expression dialog :-

=============================================================================================================

As you can see in the above context expression, it is three cost 'value' fields.

We could edit this if we so wished to be only two of the three available. Its up to us!

Lets continue to explore what is available :-

=======================================================================================================================

And some very useful aggregates too :-

=======================================================================================================================

Oh! above we see a rich set of choices for us to use ;-0)

Now then guys, off you go and do some homework on the above !!!

You need to experiment a lot with the Expression dialog - get yourself VERY proficient

Get Good !

PHH - 5th. February 2018.


Using the Report 'Designer' surface ...

If we are to use Microsoft's 'Reporting Services' then we MUST get familiar with the reporting "designer surface".

It has a lot of functionality built into many different parts of it - but it is what we would say today is 'clunky', and

it certainly does NOT look modern !

However, because it looks and feels old fashioned, is probably one of the reasons that we get it for no extra cost

when using Visual Studio - and remember, "beggars can't be choosers".

The good news is that with a bit of effort, and some time spent experimenting, we can start to do nice things,

even without going to the documentation or video clips.

Yes, I just had some 'fun' fumbling with the features and functionality.

I now have a report which toggles between two sub-sets of data - based on Order dates. So I suppose we

have early orders and later orders.

Oh! and we have a few totals columns and end of rows too.

Lets go at this just one step at a time, learning as we go.

Lets start with an existing RDLC file / object as part seen below :-

[ .. notice the surroundings of the perimeter at top left of the design surface area ..]

========================================================

Now if we click the mouse pointer while it is within the shown boundary, and before

the lowest horizontal line, we get a grey extra section at left and top - check this out :-

=========================================================

These extra 'cells' are better seen if we magnify a little :-

==========================================================================

The top left square is for the 'Tablix' itself - so right clicking will bring up its context menu.

On the left we have a cell for the Headers and one for footers, at the bottom.

The cell on the left with the three horizontal lines is for the 'details' or 'body' of the report page. So

it represents all the rows / lines of the report excluding headers and footers.

We noe need to have a look at what is on offer in the context menus which can be brought up

by a right mouse click while the cursor is in the relevant screen / designer areas.

Lets start with the top left corner 'square' cell - this represents the 'Tablix' itself.

[ .. by the way the 'Tablix' is the main table type grid object we see, holding the others cells .. ]

here are some Tablix options :-

========================================

Now the Header row - we only have one in a simple report :-

======================================

And now the details row/line area :-

======================================

And then the Footer row - again we have just one in our simple report :-

===========================================================

Looking at the few images above, it is not a surprise that we have options to add and remove rows.

An interesting other option is for 'Visibility' - we will see this in action later, as we toggle between

older and newer order date rows.

Now lets see the context menu options for a typical column 'cell' :-

==================================================================================

Yep! - so we can insert and delete columns, and also change the visibility. This will

be similar to the visibility settings we will do soon for rows, but in our sample we will

not use the option.

Okay then guys, what options do we get for a cell in the Footer ? Check carefully below :-

=============================================================

Now then folks, we see above on option for 'Expression' - this will be a VERY useful and well used feature of

Reporting Services reports we design.

We need to get VERY familiar with its use, as we can use it to great effect in our reporting.

Just remember, some or all of this is a little 'clunky'.

Now you see it Now you don't ! :

Okay folks, lets checkout the 'Visibility' feature.

Below is the dialog window we get for 'Row Visibility'. And you can see some of the settings and inputs I have

made as well.

You will be able to work out that all order rows/lines are hidden if their order date is less than July 7th. 2017.

Study these details carefully :-

==========================================================================================================

*** REMEMBER *** that "Fields!OrdDate.Value" is simply the order date to you and me.

And 'CDate' makes a suitable date from a string as seen above.

Since we are discussing in this section the designer surface features, we will not discuss the totals / summations.

What we do need to discuss is what name is given to an added TextBox shown in the following image :-

=============================================================

Well all we need do is highlight it and look in the Properties Pane (PP).

Above you can see that it is 'Textbox126' - so we may as well use that name for it.

Lets see the name in the PP :-

===========================================================

Okay then, if you like, lets try and re-name it "ToggleBox" !

=========================================================

And does this 'stick' in the programming / design 'system' ?

=========================================================

It would appear so ...........

Lets now check the drop list of graphical elements that is provided at the

top of the Properties Pane :-

Here is the look or the top of the list :-

==========================================================

And here is where we can see it now - unfortunately the alphabetic order

places the 'ToggleBox' entry at the end/bottom, but not because we have just

renamed it!

Check this out :-

============================================================

=========================================================

Okay guys, time to move on a pace or two ...........

The following screen capture that I made a bit earlier, is now out of date ;-0)

Notice that it refers to 'Textbox126' and not the new name of 'ToggleBox' :-

============================================================================

So we need to revisit this dialog to check for updating.

Okay, we seem to have 'blast-off' ;-0)

The Change Display Option dialog correctly shows (and offers) the 'ToggleBox' item :-

=========================================================================

So here is how the portion od the report screen surface looks after renaming.

We see below both views of the report designer - without and with the active boundaries.

======================================================

And after we click within the report area / boundary :-

================================================

Lets move on ..............

Testing our Efforts :

Lets now compile and run our trial / testing appllet.

Here is what appears on screen before I make any inputs with mouse or keyboard :-

===================================================================================================================

Note that the selected GUI element now has a ' + ' icon to show that it is active for clicking.

The earlier dates should be hidden, and so the one showing is a more recent date.

Lets now click and see the other which were previously hidden :-

=================================================================================================================

The two lines (rows) shown above are earlier orders.

Now lets add some more order information / data - to make our report display a little more meaningful - check below :-

======================================================================================================

And now when we compile and run, we see the improved report data display - first the earlier order date rows :-

==================================================================================================================================================================

And now the later order date lines - check these thoroughly yourself :-

Date for toggling is "7/7/2017" - check both below and above for different (but complimentary) data sets :-

[ .. less than this date is set as 'hidden' .. ]

===================================================================================================================================================================

That all looks very good, and interesting too.

Lets move along and learn some more interesting 'stuff' ;-0)

Now, for the next sub-section, I will try and get a section of the columns to toggle - fingers crossed.

PHH - 5th. & 6th. February 2018.


'Toggling' columns - widen / narrow the report ....

Okay then guys, we have seen previously how to collapse rows, or more rightly how to show and hide rows based

on some logical condition, so how do we now do something similar but with columns instead of rows.

If we look at the image below, which as this new capability built in, we do not see the 'ProdItem' and 'OrdDate' columns

which number four in total.

In the following image we see a second 'button' area called 'ToggleColumns'.

This is the one which has been added to close up and widen out the report as far as number of columns is concerned.

The first one, called 'ToggleMe' was the original (first) one which logically swaps between old orders and newer ones.

Have a general good look at the following (and working) details :-

==============================================================================================================

*** Remember *** that the 'Tablix' control used for reports, needs to have 'TextBox' items/objects placed within each

'cell' being used.

Here is an image confirmation that the main report object is of type 'Tablix' :-

=======================================================================================

Before you see the correct details in the PP you need to highlight (place in focus) the correct

item on the Designer surface. [ .. 'PP' is Properties Pane .. ]

Now try and select the TextBox item (this is not easy until you know where to click) and view

the PP - you should see this below :-

===================================================================

The 'Name' property is now "ToggleColumns" because I changed it manually.

Here are all the other Designer surface objects - many textbox elements :-

=========================================================

If you have not already studied the Toolbox for the report Designer surface, then go now

and have a careful and detailed look at it - here is part of it :-

================================================================================================

At the moment I have used only the 'Textbox' in my editing / creating.

As I get more adventurous then I am sure I will use much / many more - and to good effect.

Lets move on ................

When we slide a Textbox element from the Toolbox we get an un-styled result as seen in

the following image :-

==========================================================

To make the new text and textbox look like the other cells in the footer row, we need to apply

styles with font weight and background colour.

This is easy enough if you are an experienced 'designer' type of person - designer in the general

sense of the word.

Remember that the PP is your friend in setting and changing most/many things - checkout this :-

=====================================================

So if we use the properties input boxes and features we can do this for background colour :-

==================================================================

And also this for the Font - at the moment it is collapsed - so we get a summary :-

======================================================================

But expanding the folder we see this :-

====================================================================

Okay then folks, lets move on a pace or two ..............

Once we have a new cell (textbox) which we can use as a toggle switch, we need to pinpoint

the columns which we need to set as able to be hidden, or collapsible.

Look at what we get when we click within the report grid, and then right click the top grey area,

and then from the context menu select 'Column Visibility' :-

Firstly we see this :-

=================================================

And finally the following :-

=====================================================================================================

Notice that in the above dialog window for 'Column Visibility' we have set the initial setting to 'Hide' and

that the item to be used as the toggle is our new 'ToggleColumns' element.

Out of interest, you may like to know that we could actually choose any other 'free' Textbox item as long

as it does not get hidden by any possible toggling - either row or column hiding. This needs a bit of practise.

Lets move on .................

Before we rush off and hide the other three columns in our report design 'plan', shall we explore a hopeful

possibility - like that of setting the properties for all four columns at once - seems like a good idea !

Well, when I tried to do four at once, I started with the highlighting / placing in focus, and did the following :-

====================================================================================================================

In the image below we can see that for multiple columns highlighted, the visibility column is greyed out :-

===============================================================================

And this seems to be the case for columns which all individually do accept the visibility settings.

It is worth you trying to multiple select and then change properties for things like Font and colour settings,

simply because it often works in other design applications.

Lets give it a go and select the whole of the footer row. This can be done either by selecting a cell one at a

time and holding down the 'Ctrl' key, or by selecting the whole row from the LHS grey outside 'area'.

Checkout these details :-

=============================================================================

And then the result after clicking :-

=========================================================================================================================================================================

Well, the result looks OK - if you like that sort of thing ;-0) Tomatoes that is !

Okay then, lets finish off the testing of our report - using both the toggles we have set up - check this :-

============================================================================================================================================================

Above we have expanded the columns to full width, and also toggled the orders to show the earlier ones - or earlier 'set'.

Okay guys, enough for now I feel.

Lets move on to another sub-section and learn some more 'fun' stuff ............

PHH - 6th. & 7th. February 2018.


DevExpress Reporting

The reporting features and facilities from 'DevExpress' (DX)seem very promising indeed.

*** Note *** these are all quite new to me, as I used to do all of my own reporting and printing in VO and .NET.

I am starting to design and create some basic report types here in my office, and think that the look / feel of the

software tool(s) is just great - very nice to use.

I suggest that we take some time to explore DX and what it has to offer us. Try this link :-

[ .. and by this I mean quite a few days .. ]

https://documentation.devexpress.com/XtraReports/2162/Reporting

Now lets see some of the information on offer on the DX site :-

=======================================================================================================================================================================================================

As you can see above we are just interested in this topic section on Reporting. And Reporting with WPF seems

to be a section on its own.

It also seems obvious that DX does a lot more 'stuff' other than Reporting. More on that later, in another topic area

of these eNotes.

When we get to think about it, the reason we have all the WPF controls available to us is that the 'Designer' control

and the 'Previewer' control are built using these same WPF tools from the DX toolbox.

So we can use the tools from the WPF toolbox in our own WPF forms - there are many very nice tools.

Here is some more information :-

=========================================================================================================================

Arrrh! - this is more like what we are after, we need on our WPF form both a 'designer' and a 'viewer'.

Note they call the viewer a 'PreviewControl' - presumably because it is a pre-view before printing a hard copy !?

We shall find out as we go along, and do some experimentation, creating a few of our own reports.

Now then guys, most Reports are only possible if we have some data on which to base them.

[ .. More on 'Static' reports in later sub-sections .. ]

Data is required for reporting :

It would appear that just about anything in the 'shape' of data can be thrown at the reporting system.

However, where does that leave DBFs ? Are these in "just about anything data-wise" ???

Check this statement :-

=================================================================================================================================================

I need to ask Robert and the Forum guys about this. However, we can always read DBF data into memory and

create business class objects form it, and then form Collection objects from these.

Collections can easily be used to form Reports - so DBFs are not yet dead ;-0)

Now then, moving on, we can easily get both typed information (documentation) as well as video clips on how to use DX Reporting.

Their web provision is really good and useful too - here is a sample of 'YouTube' availability :-

===============================================================================================================================

Okay then, so there is more than sufficient information you us to teach ourselves the 'how to dos' of DX reporting.

Currently I have used the static written / type documentation to learn a whole bunch of things - a lot of stuff.

All we need is time ;-0)

Who are 'DevExpress' and where are they based ? :

When we invest time and some money into new software, purchasing and learning / training, we need to know a bit about who makes it, who sells it, and

whether it is likely to be always available in the foreseeable future.

Lets see what the web site says on this :-

===================================================================================================================================================================

Okay so far so good - BUT - we still don't know where they are - check below :-

=================================================================================================================================================================

Well, its Glendale California according to the above address.

Lets check this out :-

===============================================================================================

With all the funny / strange things happening in the world (including 'Brexit' and 'North Korea') it may be

wise to consider carefully which country is the source of our crucial software.

Some more (extra) information about DX (DevExpress) :-

==========================================================================================================================================================

So remember, we are interested in 'Reporting software' which handles the WPF human interface, and

also does Reporting and Printing, from as many standard data forms as possible.

DX certainly seems to do this. But don't expect such a great product for FREE, or very little cost.

I need to research the sales / marketing strategy. Sometimes small software houses can get a reasonable deal.

I have shown the current pricing structure later in another sub-section, just a first child folder from here (this one).

Lets move on .............. and do some reporting ;-0)

PHH - 30th. & 31st. December 2017.


What the Users say ....

The following 'genuine' comment is worth reading. It came in an email response to me with some

more help information to me from Nick.

He has helped me a lot over the years, getting my application development 'style' modern and up to scratch.

"Yes, the DevExpress support is superb.

I think that every time I've consulted them (going back over 6 years now) I've ended up

with a solution one way or another, and the 'tech' support people really know their stuff.

And rather than just saying "you need to do x", they normally include a sample project

in the solution which shows working code with the solution.

I know quite a few people say they avoid third party products so they can keep control

over all their source code, but I haven't regretted using these products one bit, and of

course the sheer quality and range of controls is amazing and lends a real polish to the

finished product that we supply.... our users love it.

Nick Friend - Cardiff, UK."

PHH - 19th. January 2018.


Some current costs and pricing ...

*** Remember *** that the support offer by their Ticketing system seems to be included in the licence fee.

This is a big 'issue' to consider, as the DX product is a very complex one - something not to be tackled

alone.

=============================================================================

Hi guys,

Once you get to use the DX tools / frameworks no doubt you will be as impressed as I am.

[ .. there is a free 30 day trial period, I strongly suggest you try this, give it a good 'go' .. ]

In which case you will be interested in the pricing structure of the DevExpress products - here are some details

at or on January 15th. 2018.

UK currency - GB Pounds (Sterling)

$900 is currently worth £656 and €733

===========================================================================================================================

and $400 is almost £292 at today's prices (exchange) - January 14th 2018.

or €326

see below for renewal costs :-

=================================================================

From what I have used, and seen for the WPF and Reporting 'module' these prices are well

worth all the features and functionality that they appear to offer us.

PLEASE note that this stuff is VERY complex and so it is not possible to get to grips with all

aspects of what is available and on offer.

Even sitting here for many days at a time exploring and testing, only scrapes the surface of

what is available.

However, one thing to really make an effort with is the GUI Designer, and that feels GREAT !!!

PHH - 14th. January 2018.


Concerns of some developers ...

Okay then folks lets focus on some real concerns that some developers have about both frameworks

from Third Parties as well as using GUI 'Designer' tools.

Let us discuss in detail the 'ins and the outs' or the 'pros and the cons'.

My 'Non-Concerns' :

Over the years I have seen many forum and news group postings about guys being uncomfortable with

interactive GUI style Designers. And surprisingly they (the design tools) often get poor press and comments.

My first real experiences with such GUI design tools was back in the days of 'CorelDraw', and 'Aldus PageMaker'.

Back then the user had no choice but to get to grips with on-screen mouse and key driven 'element' design.

It was just as well that I had to make an effort, as it now serves me well with the XAML editor in Visual Studio,

as well as the Report Designer in DevExpress.

On my way the where I am now I have passed through Chris Pyrgas's VIDE WinForms designer, as well as the

WinForms designer in VS.

I now enjoy using the on-screen Designer in both XAML and DevExpress (DX).

Concern of Others !? :

I will be very honest with you, almost brutal I would say, and tell you what I think of the posts complaining

about on-screen GUI design tools!

It seems to me that many guys write complaints before they have even given the Designer tools any sort of

reasonable 'go'. I suppose they don't know what they are talking about.

Often the GUI design tools look simple at first sight, and so guys expect to get good and quality results VERY

quickly. This is NOT how it works at all !!!

The two GUI designers I have mentioned above, that I use regularly these days, are really quite complex and

powerful tools. They have MANY features and facilities, and need to be learned during must exploration

and experimentation.

I now have a phrase I use a lot, it is "the Designer is your friend", and I totally believe this.

In fact the 'Designer' is my VERY good friend !

BUT - it is only my friend because I spent quite some time learning how to use it, and how to get the most

(best) from it.

Time spent, or invested, "up front" will save you lots of time 'down the road'.

Trying to cope with the Designer while under pressure to deliver a project on time is just - NOT - the way to

go about 'form / report' design at all - don't do it this way !

Other Concerns - "Code vs GUI drag-drop" :

Some other guys confess to hating GUI tools, and say that they wish to code everything. And that by coding

they understand more etc., etc..

Well, in some ways I know where they are coming from - BUT - hey! we need to move forward and be up to date.

The world is such a complex place, we can't do all of the 'stuff' from the ground up.

I don't know what happens under the hood of the .NET Framework, yet I use it successfully every day.

So what's the difference?

I have to confess it took me a while to get my head into using and appreciating 'Wizard technology'. Now I

think it makes VERY good sense. I am forced to stay within the confines and possibilities of a known

process, so I can do anything 'silly'! Well, that's the theory.

PHH - 12th. & 15th. & 31st. January 2018.


Documentation from DX on their tools ....

One of the benefits, and there are many, of using a third party quality Reporting framework,

is that there is usually a whole host of support material available.

The support material ranges from tree structured documentation, through to video clips.

Here I refer to material from the Third Party company itself, and not the user related blogs.

Personally, I prefer to stick to professionally prepare support material, since these professional

guys tend to have a better overall view and understanding of things, and what their product offers.

Here below is a link to the DevExpress (DX) documentation :-

https://documentation.devexpress.com/WPF/7850/Controls-and-Libraries/Reporting

And here is a flavour of what is on hand - I am looking for material on reporting from an in-memory business object :-

============================================================================================================================================================

Remember we are looking for a data source which is an 'in-memory' collection of a business class.

Here is the next level down the tree :-

================================================================

And a bit further :-

========================================

In fact we can access the 'In-Memory' data approach by use of the Wizard in the Report Designer :-

==============================================================================================================================

The above is not mine, but part of an example.

Lets follow the wizard a bit further :-

====================================================================================================================================

If at the time of using the wizard there is no data, then we can use the second option.

However, I have now used both quite successfully, and even with Enums as field parameters.

More on this later.

Lets see the fields on offer with this sample we are following :-

==================================================================================================================================

And above we can see the content of the graphical surface in the Designer that the Wizard creates.

Much easier than doing it by hand coding ;-0)

Now then guys, here below is an example do entirely by myself with my X# code providing the 'Person'

class business object Collection.

And as well as standard data types, we also have an 'Enum' for the Gender field - this was done as a 'fix' by the Help Desk :-

===========================================================================================================================================

The current work around for Enums is as follows - line 30 has to be included :-

==============================================

This is used in the Serialization - the process of putting a class object into storage - usually to disc file.

Here is the Person class which uses the 'Enum' in place of an earlier version which used strings - "M", and "F" :-

================================================================================================================================================

I will now leave it up to you to find and follow through topics within the documentation.

Good Luck, and good reading.

And remember, "learning is fun!" ...............

PHH - 8th. & 31st. January 2018.


'Support Desk' - availability and how it helped me ...

The topic of 'support' and the availability of a 'Help Desk' is one which many guys shy away from.

Particularly us technical oriented guys, who probably from past experience think of help desk persons

as those who advise "turn of the power and wait 30 seconds before powering up again ... " and without

much technical knowledge to actually solve our problems.

The other aspect of 'Help' is that we are aware that good support costs money. And we as techy guys

don't want to spend good money on something in which we have little faith.

Well, be assured that DevExpress have addressed these issues well in my opinion, as well as Nick's.

In the image shown below you will see the highlighted item in the DevExpress menu is to submit a request

for support and in so doing generate a ticket for the automatic ticketing system.

Support is built into their account costings and is also available during the 30 day trial period.

So far I have submitted three tickets and had successful and top class service / support. See below :-

=========================================================================================

Once you are in their ticketing system you get browser links like this shown in the following image :-

====================================================================================================================================

So far I have had quality service from their support desk / guys.

However, do appreciate that these guys are NOT there to answer queries that you should solve for yourselves

by using their quality on-line documentation.

The reason I used support and the ticketing system was because I was convinced that there were errors in

some of the deeper parts of the WPF controls.

So far two of my problems have been solved by them, and the third one has just been submitted, so we will

keep out fingers crossed for that too.

To be quite honest the Support Desk is one reason I will buy a licence fro DX after the Cologne conference.

Reporting done at a level by companies like Infragistics, DevExpress and Telerik (to name but a few) is so

complex that we developers need both quality support as well as comprehensive and readable documentation.

Unfortunately, Open Source 'stuff' often falls very short on the documentation side of things.

A sample of working code is NOT a substitute for quality and comprehensive documentation.

So before you decide on which third party product you are to invest in, one thing to do as a MUST is see if

the support is included in the price of the licence as well as seeing if it is good. And definitely checkout that

the documentation and video clip support is up to scratch - 'up to the mark'. Good, that is.

PHH - 31st. January 2018.


first sample report created by myself - with X# ...

*** Be Aware *** the following images came from me using version 15.2 of the DevExpress tools.

The rest of the eNotes on DX will be based on version 17.2 - but everything still works okay.

=====================================================================

Okay then folks, lets do things the "back to front" way ;-0)

Let us start by seeing the results / output from our first basic report, before we go and

look at how we created it in the designer, or run it in the Previewer tool.

Oh! and by the way, it uses real data !

Check out this simple report :-

=============================================================

Yep! - looks like three fields (or columns) and three data records or lines of data.

Okay then, what does out SE (solution explorer) pane look like in Visual Studio (VS) ?

Below, ignore the first two C# projects, I was just getting my first 'grips' on what was required.

From the highlight (blue) bar check out the lines below it.

Notice that we have a small X# project which is XAML based :-

=================================================

Simple enough it would seem, but what does the XAML script look like ?

Amazing simple it would seem - check fully in the image shown below :-

=====================================================================================================================

Yes, one short line of script, that of line 11, as seen above.

Hey ! - Hang on guys !!

Are we saying that with this one line of XAML script we get a running WPF application which allows us to

design and create quality reports, then preview them, save them, and also print them ???

The answer is YES !!! So why even think of re-inventing the wheel, and handle our own printing / reporting etc..

So here is what the design surface looks like due to the line of script on line 11 :-

[ .. note this is from the older version 15.2 whereas later I will be using nothing but the version 17.2 tools .. ]

====================================================================================================================

Now then, what do we see when we compile and run the app entitled "WpfDXQ01" ?

Well - here it is shown below :-

=========================================================================================================

Yes, this is the empty Designer / Previewer - note top left there are no reports opened.

By this I mean that one tab gives us the full Designer tool, and the other tab the full preview tool.

When we open one or more report files, we get one or more tabs showing.

Here is one tab showing :-

========================================================

It would appear that we can have quite a few tabs showing - meaning we have multiple reports open, and

being designed - new and or edited / changed.

Okay then folks, if a file is created and saved for each report we design and save, then what do these

report files look like, or what are their secondary file names ? Check below :-

=============================================================================================================================

Okay, that sounds sensible - '.repx' where 'rep' is for report and 'x' is for 'DevExpress'.

No doubt that somewhere there will be a clever way to manage a whole body of reports, but for now it is too early

to worry about zillions of reports, when I have only made 2.5 so far ;-0)

Okay then folks, at top left of the 'Previewer' screen we see the 'Print' icon.

========================================================

To get into Preview mode click (select) the right most tab as seen below :-

==============================================

When we select the Print icon we see a dialog screen as shown in the following image :-

=================================================================================================

Oh! - don't worry, I am pretty sure this is because I am using DevExpress tools which are past their

'trial for 30 days' by dates.

I need to get back on to DX and get another registration number so that I can prepare for the Cologne

conference.

=================================================================================================

Anyway, here is the Print option screen - study it in all parts :-

===========================================================================================================================

In the above image checkout the tiny text at bottom of the illustrated page - both left and right.

This is a result of having the graphical elements on the report as seen below :-

============================================================================================

*** Sorry ! - old image, it is in fact the 31st today ;-0)

Now then, time to move on and go to another sub-section, see you there .....

PHH - 31st. December 2017.

Addendum :

Oh! should have said above, we got a LOT of software functionality for our short line of XAML script,

BUT have you seen the entries into the References section of the test app ?

Have a laugh at this, then wonder just how much work is being done for us 'under the hood' !?

=================================================================

I counted 25 entries relating to DX (DevExpress).

Lets move on to another sub-section, and learn some more good 'stuff'.

We can even start to use the registered 17.2 version of the DX tools we require for Reporting.

PHH - 31st. December 2017.


Learning and Using the 'Designer' in 17.2

Okay guys, lets do some straight talking, talking about Designer tools, and Design Surfaces.

It appears to me from reading MANY posts over the years, in newsgroups and fora (many forums)

that guys often do not take the time to learn how to use an interactive design tool.

Everyone wants quick results, but using a Design tool, or 'Designer' is like learning to paint, or

indeed any craft.

The more time you put into appreciating the many aspects of the Designer, the better friend it

will be to you.

Any 'Designer' will look poor to us, and be frustrating, if we don't take time to learn how to 'drive' it.

If any large and quality company creates a Design tool / surface it will be worth using, as well as it

also having many nice features that most standard guys never get to see or use - their faults!

Take time to explore, as fully as possible, any Designer you need to use.

I have personally invested thousands of hours in XAML 'Designer' and now find it a joy to use.

I feel that I still have a LONG way to go with the 'DevExpress' design tool - BUT - the signs are all

there that this is also a powerful and complete tool.

The Designer in DX version 17.2 :

I am using DX version 17.2 along with Visual Studio 2015 and Windows 10, insider build 16257.

I suspect the later is causing me some grief, but that's another issue.

In my 17.2 installation of the WPF framework - includes the DX toolbox for VS and the Reporting software tools as well -

there seems to be no Report Designer available from the menu bar of VS.

If I recall correctly there was a 15.2 option from the menu bar, but I could be wrong.

Anyway, in order to use the Designer we need at least to make a WPF app from the X# templates, and to

this new app add the correct tool from the toolbox - see below :-

==========================================================================

Of the two reporting controls we need the second one, the Designer :-

=====================================

If we now compile and run our new app, with just the one control dragged onto the VS XAML designer surface, we

get the following view at runtime - check all these details shown below :-

===============================================================================================================================================================================

So guys, we need to get our heads around the idea that the 'Report Designer' runs from a XAML / WPF app

of our own making - we are not using the Visual Studio IDE at all.

So when we have made the simple app, we compile and run it to see and use the Designer.

When we drag the Designer control from the toolbox to the graphical surface of our new app, there are a

whole bunch of assemblies automatically added to the References section of this app by the DX 'system'.

Here is the XAML script made when we drag a Designer tool from the toolbox, onto the GUI design surface :-

================================================================================================================

This is all we need do, the entries into the References section are done automatically by the DX support 'system'.

Here is what was added to my example Designer app :-

=============================================================

Yep! - quite a lot ;-0)

But I have had to do very little indeed, to say that I am now designing many new reports,

and running them successfully too.

We now need to look in detail at the major parts of the Designer tool - time to learn !

And time to move on ................

PHH - 12th. & 15th. January 2018.


the Properties Pane ...

The PP (or Properties Pane) is probably the most underrated of all of the panes or 'interactive areas' of

the 'Designer' control.

The 'PP' does not look sexy - BUT - just about everything can be done from it.

There is a HUGE amount of detail in the PP from most objects in focus.

Yes, the PP only displays settings and choices for new settings, ONLY for the actual object which is in focus.

An object is in focus if it has been selected (or clicked) by the mouse pointer and a button click.

Lets first of all see where the PP usually is to be found - check bottom right of the next image :-

======================================================================================================

If we now see the PP in a bigger size, and with all sections collapsed, we

see the following as an example of how the PP may look :-

==================================================================

Many, Most, or All ? of the above collapsed sections are available in the PP for

each of the objects in focus.

Notice in the image shown above that it is showing the properties for the object of Type 'Report'.

If we now expand some of the sections we see the sort of things shown below :-

================================================================

And now the lower part (on down from 'Data') - the whole thing is to long to

be fully displayed in the space available - checkout this :-

===============================================================

Now then folks, lets do a test, and see which sections are show / displayed for

the 'TopMargin' - see below :-

=====================================================================

I will leave it to you to see which of these is common to the Report object properties, and

which have 'disappeared' as it were.

Once you get to use the Properties Pane you will not worry about what is there or not.

We will tend to just get on with the job and use what is presented to us.

Floating Around - sometimes useful :

Now then folks, the working 'paned' areas within the 'Designer' can be detached from their default

position and moved around the screen. They can't move outside of the main screen area, but can

be moved and placed just about anywhere else.

As a start to exploring the pane movement look at the following image of a truly 'floating' PP :-

[ .. the light grey margins show that is is 'free' .. ]

======================================================================

Display details - what is shown in the 'PP' :

=================================

======================================================

==============================================================

==================================

======================================================

PHH - 12th. January 2018.


the Toolbox and its contents ...

Hi guys, well in many ways this is an ease or simple section about learning to use the 'Designer'.

In the images show below we have a very limited number of tool icons - many times fewer than we

have become accustomed to using in the Visual Studio XWF/XAML editor. There I have many

hundreds, possibly thousands.

Lets look at our refreshingly simple toolbox :-

=======================

Note that the expanded / part collapsed size of the Designer control automatically

determines how the tool icons 'image wrap' within the toolbox.

Here are the top few tools when the Designer is fully displayed (expanded fully) :-

=========

In fact we can do a lot with the tools except hover over them to get a name :-

========================================================

or to drag a copy of the tool after clicking within the icon area and holding the mouse button

down while we position the selected control in a report 'band'.

Here is an image of a Check Box control being dragged into the Top Margin :-

====================================================================

Notice that when we have selected a tool from the toolbox it has a blue background, or highlighting.

Here below is the line tool being chosen after hovering over it to see its name :-

==========================

I am sure we can get quite used to the toolbox and its operation, it is probably the complexity of the

actual controls selected which may give us the most concern - knowing how it works to get the most

from it in the report.

Lets leave tools for now, and return to learn something about the Report Explorer pane in the next section ..........

See you there.

PHH - 12th. January 2018.

Addendum :

We can move the panes around as seen below - parked in a very different place - but the Toolbox and the graphical

design surface seen to be fixed in position.

Check out the following for pane positions - yes, they don't make a lot of sense - BUT - they are re-positional :-

PHH - 16th. January 2018.


the 'Report Explorer' pane ...

=================================

=====================================================================

==================================================================

================================

===========================================================================================================

======================================================================

Floating 'Free' :

=====================================================================

If you wish to see a very unusual layout of the Designer then visit the section in a few's time

called "Floating Panes".

In it we see the PP in quite an unusual position.

PHH - 12th. & 16th. January 2018.


the Menu Bar and Tabs area ...

===============================================================================================================================================================================================================

=============================================================================================================================

==================================================================

PHH - 15th. January 2018.


the GUI 'Editor' surface ...

Although the GUI 'Editor' surface looks quite simple when we start a new report 'page', it is

in fact more complex than it looks - AND - we need to understand all of its parts, as well as what

they can do for us and our reports.

Lets look at the top part of this editing and design surface :-

===========================================================================================================================

In the above image we can see that we have ruler 'bars' for vertical and horizontal measurement.

And probably most important of all is to see the two different horizontal sections separated with a light grey dashed line.

There are actually three 'page' regions, or areas. They run across the report page.

Here below we can see in the very collapsed, or 'shrunk in size' image these three - with different colours on the LHS :-

========================================================================

I am not too sure just what anyone with colour blindness will make of the three 'section tabs'.

We can refer to them as 'Top', 'Body' (or middle), and 'Bottom'.

This is what shows in the Report Explorer pane (RE) for the basic simple report page :-

==============================

What do the Three Regions 'Do' ? :

Okay then folks, lets learn some more 'stuff' about the editor regions, BUT, without getting too

complicated and out of our depth.

When trying to learn a new interactive design tool it is best to go so far, but then try and use what we

then know, to get some useful and meaningful results.

In the image below notice that the naming of the top and bottom 'margins', as well as the central 'Details'

section (or band) have all got a numeric 'post qualifier' - in this case a '1'.

To me this suggests that we can therefore have more than one of each of these. Fits checkout the

standard or default page structure of our new report :-

=========================================

In the above image try and see how there is a light grey dashed line showing where the sections start / end.

Note also the vertical dashed line which indicates the left hand margin - there is a corresponding one at the RHS.

By clicking, dragging, and then dropping the dashed boundary line we can increase the Details1 section

as shown in the following image :-

==================================================

Be aware that when previewed and/or printed, the software adjusts the middle section to fit the

printers page length with the defined Top and Bottom being as they are viewed on the design surface.

So top and bottom are 'get what you see' and the middle is adjusted accordingly.

Now lets see what the menu bar 'Page Setup' icon can offer us :-

================================================================================

Oh! - I see a default paper size setting which causes UK guys grief !

Notice that we can numerically change/set the margins, as opposed to interactive 'dragging'.

For more detailed information on the regions of the Report 'Page' and how to create and

manage them, visit the first child section from here ...............

See you there for some learning 'fun' :-

PHH - 15th. January 2018.


Navigation: 'Printing and Printers', and Report generation too ... > Third Party report generators for .NET > DevExpress Reporting > Learning and Using the 'Designer' in 17.2 > the GUI 'Editor' surface ... >

Return to chapter overview

Setting and Managing 'Bands' ...

In this sub-section we are going to explore in more detail the various parts of the Report 'page'.

We are not going to try to use all the more complex 'stuff', but we do need to know what is available,

and/or possible.

Some of use are probably VERY unlikely to use some or much of this stuff ;-0)

Quite often simple reports are the most powerful for standard office folk, as well as their managers!

Okay then guys, for 'fun' lets scare ourselves just a little - see below how complex a report page

can become - I suggest we keep away from such pages until we get REALLY GOOD !!!

Check this out, all the details :-

===========================================================================================================================

In order to get the image into the pages of these eNotes I have had to squash it just a bit.

This means that some of the 'Band' names have becomes ' . ' or periods.

However, we can clearly see Page, Report, Detail, Top and Bottom. Yes, there are three middle or

details sections - why - just because it can be done from the management fly-out options ;-0)

In fact we can even add a couple more 'Bands' - those for Group Header / Footer.

Below I have removed (deleted) two of the Details bands and added those for Group - check this :-

==========================================================

Do NOT get alarmed, we will have much simpler reports than any with the above structures.

How do we add and remove report page 'bands' ? :

Lets look at the following small image, I am currently just placing and hovering the mouse pointer in and

over the top margin 'tab' type of area :-

============================

If we now right click the mouse pointing within the top margin shape / area we get a context

menu from which we can select - see below :-

==============================================

Okay, so we see that we can 'insert' other bands, and detail sections.

What do we get when we ask for the context menu while the mouse cursor is in the 'Details1' ?

See below for a similar menu but with small differences :-

==============================================

Okay, what we may suspect, but what about the 'Add SubBand' option, lets see what that does

when we choose and click it :-

=========================

Oh! very nice - BUT - remember we are going to start with simpler Reports ;-0)

Lets hover over the Bottom margin and then get a context menu :-

===============================================

Yep ! - looks similar to the Top Margin context menu that we saw above.

Adding some more 'Bands' :

Okay then folks, lets be brave and try to add a header band - it seems as though

we can do this from many places in the Bands tabs - here we click in the Top Margin band :-

====================================================================

And the result of the selection is as seen below :-

=====================================

Yes, we have above an extra band called 'PageHeader1'.

If we then do the same for a page footer we see the following result :-

=================================

Yes folks, that looks quite neat !

Lets now be more adventurous and see about adding some extra detail bands and sub-bands :-

================================================================

Which results in the following report structure :-

=================================

Note that the new band has a blue border showing, well lets look at the RHS of this

to see what is available for editing 'mode' :-

=================================

If we click the 'chevron' small icon we get editor options as seen below :-

==================================================================================

And as we may have expected, because we have a report detailed band, the options revolve

around data.

Remember that most report have data from a data source bound to the fields displayed within the

details bands and sub-bands.

The data source can be directly from a back-end database, or indeed an in-memory .NET object.

Personally, I prefer Collections of my business class objects as shown in other folders of these eNotes.

Now then guys, lets take a complete overall view of all of these report bands - check this out belwo :-

==========================================================================================

Yes, this is in fact the first image of this eNotes sub-folder - where we started ;-0)

Hopefully, we can feel more at ease now with the banding parts of the Report Designer tool.

*** Do Remember *** We still have MANY things to learn about report styles and structures.

BUT - the Chinese say "Even the longest journey starts with the first step! "

See you in the next section ................

PHH - 16th. January 2018.


the Group and Sort pane ...

==========================================================================================================================

================================================================================================================================

================================================================================

================================================================================

PHH - 15th. January 2018.


Navigation: 'Printing and Printers', and Report generation too ... > Third Party report generators for .NET > DevExpress Reporting > Learning and Using the 'Designer' in 17.2 > the Group and Sort pane ... >

Return to chapter overview

a Group within a Group - sample ....

Okay then guys, this sub-folder is 'fun' and quite interesting as well.

We will group out data records by two separate and different fields, 'City' and 'Gender'.

And then we will make a couple of simple changes to group then by 'Gender' and 'City'.

We will also make a screen GUI change to reverse the order of both of these ..... yes, 'Fun' !

Lets start this section by looking at a successful report preview - study the full details of this report below :-

=================================================================================================================

Yes, it looks good and is quite understandable, regarding the 'group' that is.

But since we created the 'GetPerson' data list we have added another 'Gender' property and provided an extra 'field'

of data therefore - so we need to check this out - see below :-

====================================================================================================================================

=========================================================================================================================================

I tried an 'enum' first and got some issues, so I fell back to trying a more basic approach with strings.

I will investigate the issue later and check with the DevExpress support guys.

Okay then, having used the Wizard in a similar way to previously seen, in earlier sections, here is the

report design the Wizard came up with :-

====================================================================================================================================

And all the elements have been placed automatically in the Report Explorer (RE) pane

check below for all the details :-

==========================================

Now then guys, something up till now we have more or less ignored, the "Group & Sort" window.

Lets see what this looks like for our doubly group report and how we may use it to effect some changes.

Check these details :-

============================================================================================================================

They were created along with the other report 'stuff' by making the following selections in the Wizard :-

======================================================================================================

The above seems simple enough, and familiar.

But the next dialog screen is important as previously we either left it all blank or

provided only one field.

Below we have selected a nested pair of fields - City being first :-

==============================================================================

The order of selection is important, as we will see later / below.

Lets now look at the report when we place 'Gender' before 'City' :-

=============================================================================================================

And here is the relevant dialog screen, with the order reversed for the grouping :-

====================================================================================

Okay then, now lets focus on the Group& Sort window - checkout these details :-

====================================================================================================================================

For now lets keep the grouping and sub-grouping as it is, and just see what can be done about the sort order.

Lets see what is on offer in the drop down box :-

============================================================================================================================

Oh! so we can change both sort orders to descending if we choose - lets do it :-

============================================================================================================================

And now we need to click the tab for the Preview to see what we get - checkout the following image details :-

===========================================================================================================

And if we look carefully we see we have 'Gender' going from M to F (which is descending), and 'City' going

from London to Birmingham, which is also descending.

Some good results in this section, time now to move on ................

See you in the next sub-section.

PHH - 19th. January 2018.


the Wizard - 'how to' - with it ...

Okay guys, how many like using Wizards, and how many of you don't ?

It took me a while to get into the habit of using Wizards when they first come out for public

consumption - many years back.

Now I like / love them, but I do realise that anything that can be done using a Wizard can be

done using other menu option tools, and even code itself.

In many a sense, Wizards are a super version of context menus. Because we only get offered

to us as choices, what is available or possible without cause any errors / issues.

Which Wizard are we talking about ? :

In this section we are going to look at the 'Report Wizard'.

That is a series of dialog screen providing us with choices to be made for designing the basic

structure of one of the many possible report types.

In the image below we see that the Report Designer control is open for use, but as yet has no

open reports.

I have clicked the ' + ' in the top left hand corner which brings up the first screen of the Wizard :-

====================================================================================================

The above image shows the default setting for the first screen of the Wizard, the minimal use of the Wizard

features is chosen if we select the 'Empty Report'.

==================================================

Then we only get a 'Finish' option and after clicking we see the following :-

[ .. The 'Finish' button is bottom right of the above screen image. .. ]

==============================================================================================================

The basic elements of the GUI on-screen editor for an Empty Report are shown in the above image.

What I mean by 'empty' is that it is the basic 'canvas' on which we can build any type / style of report by

doing everything ourselves - the hard way, using individual property settings and the likes.

It is often much easier to use the Wizard and let it make the settings for us, MUCH easier I find.

Now then folks what do we get if we close the first Wizard screen with the top right red cross box ?

Well the answer is shown below and is "not a lot" !

=================================================================================================================================================

Yes, the only thing we can do now is try to open an existing report - like this below :-

=================================================================

Okay then, since we are at this stage, we may as well see how to open an existing report.

Remember, the '.repx' file is the blueprint (or plan) of the report :-

=======================================================================================================================================

Once we have selected the file shown highlighted above, we get the report showing in the design surface as seen below :-

================================================================================================================================

Obviously, while reading in the 'blueprint' details and object settings etc., the relevant properties

were set and can be seen directly in the Properties Pane window.

Look at the mainly collapsed form of the PP window below, but the Paper Kind indicates that we

have a 'Report' object in focus :-

===================================================================

Now then guys, we need to take heed of this, as paper size of 'Letter' is bad news for UK guys.

I suspect that 'Letter' is also bad for all European folks, as it makes the printer 'stall' or lock up

at the time of printing.

My advice is to go in to this setting a soon as possible and change it to 'A4'.

===========================================================

And here is the change when completed :-

=====================================================================

Okay, lets move on a pace or two ...........

Going further with Report 'Wizard' - binding to data:

=============================================

===============================

================================

============================================

=================================

===============================================================================================

=================================================

Choosing a Database - to provide binding data :

============================================================================

===========================================================================

=============================================

========================================================================================================================

=======================================================================================================

================================================================================

==========================================================================

===============================================

Before we move on to completing the Report, and running it, lets take time to checkout

the 'Server Explorer' pane of Visual Studio.

We can use the functionality of the SE pane to find and view SQL servers and their available

databases - and hence to the Data Connections to view the data tables available.

Check the details shown below and you may then recognise these again when we see other

screens from the Report Wizard :-

=============================================

Okay then, the following image should make more sense now you have seen the

previous image.

===================================================================

Lets choose the 'Stock' database for use with our simple report :-

===============================================

Now below we need to make a selection, we will keep it simple :-

=====================================================================================================

And to keep it simple we will just select Tables :-

=======================================================

Okay then guys this is possibly where I made it more complex than I needed to for our first

Wizard driven report design !!!

Lets just go with it for now and see what we end up with :-

=====================================================================================

We only have two data tables in the Stock database, so lets select them both :-

===========================================================================================

And to try and keep things simple we choose just the 'Product' ones :-

===========================

We are not done yet, we get a 'GROUP'ing dialog screen, and for a first start we should

probably keep away from Groups.

[ .. we will see a good Grouping example soon when we use an in-memory object .. ]

Lets keep going - we see the boxes to be filled :-

====================================================================================

And going even further :-

==================================================================================

Below I should have made a simpler (possibly one) selected field entry - but we will see

as we keep going :-

=================================================================================

Lets make some sort of a selectio.

====================================================================================

And going even further ...........

=============================================================================================

Now we are nearly done :-

*** CARE *** - this is a further example screen to suggest that I went wrong - I should have ignored the Grouping !!!

================================================================================================================================

Lets just keep going to see what we end up with as a 'result'.

============================================================================

Okay guys, a bit easier, we can all dream up some sort of a title :-

===================================================================================

And here is my title :-

==================================================================================

At last, after many screens I have to click/press "Finish" :-

=============================================

Okay, having made some sort of an attempt at using the Wizard - we will get better - here is the Report layout made for us :-

================================================================================================================================================

And to match the above we see entries in the RE pane as seen below :-

======================================

Do please remember that this is probably way more complex that our first few reports

should be, but it does demonstrate the power and flexibility of the Wizard.

And to run the report we need to call up the use of the Preview tab - see the following image :-

======================================

Below is a very reduced size image of the one page report that is produced after clicking the 'tab' :-

==================================================================

And so we can actually read some details, here is the top part of the report single page :-

===========================================================================================================================================================

Magic ! - without knowing very much we have made some sort of a tidy looking report.

Yes, I know, we can improve the date printed format.

More on that very soon.

Here it is ........... highlight the Date field in the Details1 'record' line and change the property :-

====================================================================

The text formatting option is chosen from a dialog drop down :-

=============================================================================================

Notice that this selection window has been launched by clicking the three ellipses to the RHS

and midway down the Properties Pane part image above.

Choosing an Object - to provide 'binding' data :

In many ways I prefer this approach - it is VERY flexible and will work with DBFs as well as all SQL 'flavours'.

We make a Collection object, of business objects like my Person class type shown below, and bind to that :-

=================================================================================================================================

We can actually bind to any of the three STATIC data methods, when we use the Wizard.

However, we can keep things simple at first and use the 'GetPersons' data. This data has

only two different Cities in the set of Persons, and so we can choose to group on City when it

comes to that stage of the Wizard operation.

Using the Wizard to make a Report based on the 'In-Memory' data object :

PHH - 15th. & 17th. January 2018.


Navigation: 'Printing and Printers', and Report generation too ... > Third Party report generators for .NET > DevExpress Reporting > Learning and Using the 'Designer' in 17.2 > the Wizard - 'how to' - with it ... >

Return to chapter overview

- 'how to' - without it

If we don't use the Wizard built into the Report Designer, then we either need to use the

menu bar icons and tools, or to hard code the functionality into our apps and projects.

We will try to cover both of these methods / approaches in this section and sub-sections.

If we are to get anywhere without the Wizard, we do need to use it for at least one screen

input option, and that is to select an 'Empty Report'.

Study the following simple image :-

==================================================

Having selected the Empty option and then Finish, we should see the following basic 'bare bones' report design :-

==============================================================================================================

PHH - 18th. January 2018.


Navigation: 'Printing and Printers', and Report generation too ... > Third Party report generators for .NET > DevExpress Reporting > Learning and Using the 'Designer' in 17.2 > the Wizard - 'how to' - with it ... >

Return to chapter overview

- 'how to' - hard coding ...

PHH - 18th. January 2018.


'Floating' Panes - handling the IDE ...

Some of us may be used to Visual Studio and the way in which it is VERY customisable.

How we can move panes around to suit ourselves, and how we can have them floating or docked.

Well here we will explore the Report Designer from DevExpress :-

Now for Something 'Completely Different :

Take some time to examine and visually explore the image below of the Designer as it now looks on my office development PC.

Look for the named panes .........

==========================================================================================================================================================================================

Yes, we can pick up a pane by a mouse click and hold in the pane Title Bar, then drag it around the screen.

As you can see below the image contains one of those mystical magical 'do hickey' things that Don was

famous for naming during a session on Visual Studio 2005 at a DevFest conference, 'way back'.

See if you can spot the 'do hickey' :-

=========================================================================================

And there is a smaller less functional 'DH' at the left and the right of the main

screen - here is the RHS one :-

=====================================================

And now another major 'DH' in the centre (ish) of screen :-

==============================================================

At some point of me messing I had this on my screen - all three panes un-docked, or floating :-

====================================================================================================

Okay then folks - below we can see three panes all docked in a shared space - so we can see 'Tabs' at

the bottom of the combined pane section - check below for full details :-

==========================================================================================================

Effectively in the screen arrangement shown above, we have three panes one on top of another. And we then

select a pane to view by clicking (selecting) the required 'tab'.

Here below are the three different selections :-

First Properties :

=============================================================================

Next Group & Sort :

===============================================================================

And finally RE (Report Explorer) :

=================================================================================

I strongly suggest that you put aside some time to have a go at undocking the panes, and

moving them around to make different set-ups and arrangements - try docking each pane

in different places around the IDE environment.

Lets move on and learn some more good stuff .............

PHH - 16th. January 2018.


An in-depth look at some useful settings ....

Various Editors and Masking for some Report controls :

Okay then folks we all like to make our input forms more responsive, and to give them a more

professional 'look - feel'.

We all like to limit the degree to which users can input 'wrong' or incorrect characters and key

strokes generally.

We have quite a lot of control of this built into the Properties Pane settings, from which we can

select options for masking and editor selection. More on this will become apparent as we show

and work through some examples.

First we can start off with an obvious example - see below for our 'Data of Birth' editable field :-

====================================================================================

In order to get the above date selection tool to be available, we need to highlight, or more correctly, put in focus

the Label control as shown below :-

=========================================================================

Then in the PP window we need to change / select the correct 'Editor' - see below :-

=================================================================

Although in the above image we see that the 'Date' editor is already selected, in fact initially

no selection is shown - I chose the one above.

Now then, in the Editors window / dialog form, there are three tabs with relevant entries - see below :-

==================================================================

First we select a suitable tab, and then one of the type of Editors on offer.

The Date-Time tab is very uninteresting as it only has 'Date' on offer.

However, the Letters tab shows the following options :-

==================================================================

Arrrh! the above looks interesting as we could use the 'Only Letters' selection

for our Names Labels - .FirstName. and .LastName'.

By doing this we can stop entry of numerics or anything which is NOT a letter, or

character of the alphabet.

Here is what I have set for the contents field for LastName :-

===========================================================================

And the settings for the above Label control is :-

================================================================

You will have to take it from me that when I tested the report with the above details

and settings, I was unable to enter characters or key strokes other than A-Z or a-z.

Here is my test inputs :-

===================================================================

In our coding we are used to talking or 'Validators' or validating rules, which we apply to text

box inputs, well the above options are well on the way to help us 'validate'.

Note that the Phone control allowed us to put in a space - BUT - that is because I cleared

the 'Integer' setting I had tried previously - this di not allo me to have a space between numeric

characters, perhaps we need a 'mask' of some sort.

======================================================================

It looks as though we can make and specify our our Editors. We could do with one which is

only numerics and a space and round braces perhaps.

Lets move on, I will keep experimenting !

PHH - 14th. January 2018.


the 'Preview' control - getting the most from it ...

In version 17.2 DX framework for .NET (WPF) we need to use the 'Previewer' either as a

single tool, or as part of the Designer tool, where it is included for us to use.

Here is a snapshot of the toolbox in VS :-

====================================

If we run a WPF app with a form including the Designer we see two tabs as shown below :-

==============================================================

So we see above that we get the Previewer in with the Designer, as a 'job lot' we would say in the UK.

Meaning, all in together.

However, if in our WPF app, we don't wish to allow the users to have access to the Design features and

functionalities, then we can simply provide a WPF form with just the Previewer available on it, as in

fact is shown below :-

=================================================================================

For us guys in our developers role, we need to build up the skills and knowledge to use both of

these tools WELL. Yes, we need to become experts!

===============================================================================================

PHH - 12th. January 2018.


Navigation: 'Printing and Printers', and Report generation too ... > Third Party report generators for .NET > DevExpress Reporting >

Return to chapter overview

Starting a WPF app which includes DX reporting functionality ...

PHH - 12th. January 2018.


The 'Static' style of a Report ...

Hi guys, this small section is going to be 'fun' ;-0)

I think the documentation from DX refers to what we are to create as a 'Static Report'.

To us ordinary guys and developers etc., it is just some page layout format which does not have fields

included that are bound to any data objects.

So no external data means that the page / sheet stays the same. Well, almost, since it looks as if we

can load text from files.

For now, lets just think that we are designing posters, or a flyer, or notice. Even tickets for 'events' if we so wish.

Two ways to Create :

As with many reports (of all types), there are usually two ways to create the report.

Many may prefer the 'Designer' graphics tool, whereas others may like the hard coded way.

Also, with the Designer, we can use the Wizard or just the menu bar type of tools.

Here shown below is a quick and easy first try I accomplished with the full help of the Designer, no Wizard!

Note that it is easy to mix text and graphics elements - in reality they are all really graphics elements, even

the text we originally supply gets converted - check below :-

========================================================================================================

Now then, how does this look in the Designer, rather than the Preview tab we used above ?

Here below is a look at some of the elements used for the above output.

We have two Picture Boxes, a Rich Text area, and a further Picture Box for the motor car.

Here is the design surface :-

===================================================================================================================================================

Note that in the RTF control (Rich Text) we have yet to do some work on changing the text properties.

First of all lets look at the Toolbox sections which contain these tools used above :-

=======================================================

We can click and slide the tools from the toolbox strip onto the design surface.

As for other reports, there are three page areas, a top and bottom margin, and also

the details section in the middle - see the RE pane below :-

=====================================================

If we expand the report sections we see some more understandable information / detail :-

=================================

Now then folks, we need to clear up a possible source of error - or should I say something which we

as user / designers may get wrong if we don't get our heads around the issue.

When I first used the Rich Text element I thought it was a way to place larger text sections on the screen,

and did not realise that behind the text control was the possibility of read/write actions to files.

====================================================================================================================================

========================================================================================================================================

====================================================================================================

=============================================================================

====================================================================================================================================================================================

==================================================================================================================================================================================

==========================================================================================================================================================================

PHH - 11th. January 2018.


Getting to grips with some of the basic design tools ...

Lets try and get more familiar with what the Designer can offer us when we create a static report

from an 'empty' template.

Here is the Wizard we get from the Designer when we opt (choose) to make a new report :-

==================================================================================

After selecting the above option, there is only a 'Finish' button to further select - then we

get this as seen below :-

=========================================================================================================================================

From the toolbox at the LHS of the screen, drag and drop a Rich Text control (element)

as seen in the following image :-

=====================================================

When the cursor shows as above we can reposition the control anywhere we wish really - BUT - it

is probably best for now to keep it within the 'Details1' sub-section.

The 'handles' on the control allow us to change the shape of the control when the mouse cursor

changes to a two headed arrow. Stretch the sides and top/bottom to suit your requirements.

Now then folks, how do e edit the contents ?

Well, we can double click the fourheaded arrow cursor - OR - probably better, we can single click the 'small'

chevron at top right of the control itself - see above.

This brings up the following Editor form /screen /pane :-

================================================================================================

We can now type within the text area, which in the above image is blank / white.

Here below is how it looks when some text has been entered, or typed :-

===============================================================================================

This is where we need to experiment, and use the menu bar options and settings etc.

Below is after I have made a few changes to font, size, and style etc. :-

==============================================================================================

After closing the editor after clicking 'OK' we see this :-

========================================================================================

So we may need to use the handles to re-size the control - above we may wish this to be

shown all in one line and so we would need to drag the side handles out a little.

You can still re-position the control with the four arrow mouse cursor.

Now then, here is where I have to report back to you that I am a bit flummoxed', or confused.

If we choose to save the report as it looks, with the new text styles etc., we get a positive 'feel'

to the save procedure - some of which can be seen below :-

============================================================================================================

Remember how the report looks in the Previewer (yes, it is a simple test) it shows as this :-

==================================================================================

The file for the report 'blueprint' (or plan) is seen below as it was safely saved on disc.

Here it is ready for opening in a new clean 'tab' for designing updates and previewing etc. :-

=============================================================================================================

But when it is read in (imported) the Rich Text seems to have lost its attributes, styles etc. :-

============================================================================================

And yes, even in the Previewer it is no better - so where have the style settings gone ?

===============================================================

Obviously I need to do some further exploration and experimentation ;-0((

PHH - 11th. January 2018.


Making some 'fields' editable ...

This interesting little section could be entitled "Having your cake and eating it."

We are to have and preview a 'Static' report, but will be able to change and edit one or more

of the text fields if we so wish it.

I am talking about at Preview time, not Design time !!!

Also, when I use the word 'field' I use it in the most general term of a piece of data in a certain position.

Of course we have to change the property of any such text control - through the PP, or

'Properties Pane' usually found at bottom right hand corner of the Designer tool.

Once the field is set to editable, then the text content control can have its content changed at time of

'preview' of the report.

This is all a bit new to me, I had always thought that all data input, or merging would need to be done

at the point at which the report was being 'formed', ready for preview.

Obviously I am wrong in the case of DX reporting with WPF.

So what do we do when we discover something new, well we try to find a good use for it !

That is how my simple test 'Willie Wonka' report was born, conceived, or created - check below

to see me adding a suitable image to the header of the report, which was found on Google images.

Once the Picture Box control is dragged onto the Designer graphic surface we need to fill it with and image :-

==================================================================================================================================================

Note above that the Picture Box has a small left pointing 'chevron' which can be seen clearly

in the following image :-

=========================================

Many more of the controls have such an icon which when clicked provides a suitable input and editing

'active' dialog screen / form.

We will see more of these as we study the various aspects of tools within the 'Designer'.

Yes, these 'active' handles are only available to us during design, and through the Design surface itself.

For now, lets look at another 'wizard' or facilities type dialog form, that for the Label control :-

=========================================================================================

Note that the 'chevron' symbol changes from left pointing to right pointing depending on whether

the dialog form is open or closed.

Check these two small images, for comparison :-

================================================================

The properties pane for an editable label control is set as shown below :-

======================================================================

So our Label element allows text changes at Preview time because of the checked box.

Be aware however, that the same is not allowed for the RTF control - Rich Text control.

If we look in its properties pane we do not see an 'Enable' property option.

After 'Can Shrink' in the PP we see this instead :-

=================================================================

So no edit option to select.

Conclusion - that RTF (Rich Text) controls can't be edited at report preview time.

We need to look more at what RTF controls can and can't do.

PHH - 12th. January 2018.


Find 'Editing Fields' ...

*** Note *** Editable fields can also be 'set' and 'pre-filled' (by default values) by code.

======================================================================

There is a very useful facility from the menu bar of the 'Print Preview' facility / control.

I have shown the 'Editing Fields' option icon in the following image - from an example in the

DevExpress documentation :-

===================================================================

We need to try this for ourselves - I will go a make a sample for testing .............

Okay guys, back again, here is the preview result of my new static report, and it has had the

editable fields changed - data has been entered.

Study all the details shown below :-

==============================================================================================

In the above example of a static report being used for form filling (without a back-end data source), we

see four editable fields outside of the grouping box, and nine within it.

These are four Label controls and nine Check Box controls. To see this we simply look at the RE (Report

Explorer) pane in its expanded form seen below :-

======================================

If we look at the report in the Print Preview control we can highlight all of the fields which change be changed.

Here is what the static report / input form looks like when the highlights are requested :-

=============================================================================================

And now we need to see the menu bar highlighting option icon :-

========================================================

We need to get an overall view of the Preview control as this is what we see when we use the report tools.

Check below for overal shape, size, style etc. :-

=============================================================================================================

The size of the form / report on the screen is expandable / collapsible, with the slider at bottom

right of the Preview control - check below :-

=========================================================

And of course we can move the 'page' or report surface around using the scroll bars which show when appropriate.

*** Remember *** The information (data?) entered is not linked to any such data field behind the report.

Think of it as being there just for one-off 'static' information.

Here is what the documentation from DX has to say :-

==========================================================================

Content Editing Specifics :

When enabling content editing in your report, consider the following.

- The changes made to a control's content in Print Preview have no effect on other parts of the document

(e.g., the related summary results, grouping, sorting, bookmarks and other settings that have already

been processed before generating the document).

- The XRControl.CanGrow setting is ignored for editing fields.

Multi-line values can only be entered when no mask is applied to an editing field. The editing area of a

field cannot exceed the original dimensions of a control.

- Values entered into editing fields are reset back to their defaults after refreshing the document

(e.g., when submitting report parameter values and expanding or collapsing data in a drill-down report).

- It is impossible to edit content of a control that has its DetailBand.DrillDownControl property specified.

- Field values entered in Print Preview for controls placed onto the Top Margin and Bottom Margin bands

are not preserved when the report is exported to TXT or CSV, as well as the following formats as a single file.

HTML

MHT

RTF

XLS

XLSX

image

Tip :

The created report is platform-agnostic and its interactive features are supported by the Print Preview

available for all application platforms

==========================================================================

Here is a way to think about the text information in one or more editable fields :-

PHH - 13th. January 2018.


Using the Designer 'toolbox' and related operations ...

Okay guys, remember the 'Designer' is our friend, and a GOOD friend at that ;-0)

We need to explore the operation of the Designer, in as many ways as possible, and build up

a body of skills and knowledge.

If at any time we feel we are 'fighting' the Designer, or are getting frustrated, then this is a definite

sign that we do not have enough knowledge.

Make time to get 'good' (productive) with the 'Designer' - it is time well worth investing.

Making a Start :

Lets start a new report - by clicking the ' + ' next to the last opened tab at the top left of the menu bar :-

===============================================

When we select the ' + ' we get the Wizard offering us a style / type of report :-

==========================================================================

For our purpose, that of doing some research and learning, work with a blank report (one not

bound to any 'back-end' or 'in-memory' data), we need to select 'Empty Report' and then click "Finish",

as that is all that is on offer for us to choose.

So now we need to drag from the toolbox a Label control - seen here in the following image as

I position it somewhere in the Top Margin, before I release the left mouse button, to carry out the drop.

================================================================

I have above used the 'Top Margin' as target drop space, simply because it was easier to capture the screen

to show and illustrate it here.

In fact, after screen capture, I moved and re-positioned the Label control inside the 'Detail1' section of

the report surface. The body of any report goes in the middle section called 'Details1' and similar.

Here is the current work area, after I have dragged more labels onto the surface and positioned them one above the other.

In fact in the following image I am trying to "marquee select" (multiple select) all of the five Label controls :-

==============================================================================

I will explain in a moment why I am trying to make a multiple selection, first lets see a couple

of ways we can make such a 'marquee' selection.

[ .. the term 'marquee selection' first came to my ears when using 'Corel Draw 1.0' .. ]

Click one control, then with the 'Ctrl' key pressed and held, click each of the other controls in order,

one at a time - see the 'marquee' area grow as we mouse select - here is the final selection :-

================================================

If you have enough room to play with on screen, I think you can move the handles on the multiple

selection wire framework - if you do then make sure all of the control for each of the elements is

completely within the selection. Give it a try anyhow, you will learn something ........

No, the above is NOT true, when I try it - instead a better way is to start with no selection made, then

hold down the 'Ctrl' key and click the mouse with the cursor at a position top left, then drag it down

to a position at bottom left.

Then release the mouse and also the key as well. Make sure that all your required controls are totally

within the wire frame, and if possible that no others are within the frame.

Now give this a try and see it work as shown below.

Check this out :-

=========================================================

It does not matter if parts of some other controls are crossed by the frame.

And, more interestingly, if you do get a marquee selection with unwanted controls included,

just hold down the 'Ctrl' key and click each unwanted control with the mouse pointer in that control,

and it will be de-selected.

Below I show an image in which I have de-selected the middle two long Label controls and I still

have the 'Ctrl' key pressed, as the mouse pointer would indicate.

Check this out fully :-

=============================================================================

Now then folks, back to where we were discussing why we need a multiple selection.

I simply wished to have an easy way to change the font style on each Label to be bold and italic.

By changing the property settings in the PP while multiple selection is 'on', we can change

the properties of all in the selection items - see my results below :-

============================

Which looks like this below, when the report is previewed :-

================================================

Ignore for now the editable content, we will change the default text soon, to something more meaningful.

Notice above that all five Labels now show the text as I required, and by doing changes to two separate properties

in the PP window. Actually, I made the Bold & Italic changes with the menu bar options - its just another way of

doing the same thing.

So what if we wish to have some more friendly default text in the four editable Label controls ?

Okay then, one step at a time. We can manually (one control at a time) change the Text property

from 'label3' or similar to ' ... ' and the result is as seen below :-

===================================================================

However, since 'Text' is a property of all of the highlighted Label controls, we can 'marquee select'

all four of the controls and the change the property only once within the PP window.

I have just done this as seen in the following images - first the multiple selection :-

==========================================

And then I moved to the PP and made an entry as seen below in the next image :-

=================================================================

The result when the report is run and shown in the Preview control is as follows :-

====================================================================

So the result of all of this 'fun' is to realise that working on, and with, controls in a mode

of multiple selection is very worthwhile and we need to use it whenever we can.

Much developer time and effort can be saved.

Oh! I need to add this extra tip about placing controls on the report Designer surface.

Placing Multiple (similar) Controls :

Lets look at a useful 'trick' or 'feature' of the graphical designer. Another way to save time and effort.

There is a neat way to add many similar controls without need to drag each one from the toolbox.

Into the report surface area in which you wish to have multiple similar controls, drag one from the toolbox

and release the mouse button.

With the selection handles still showing on this first control, press and hold down the 'Ctrl' key.

Now click inside the control's framework and drag a copy into another area close to the first one, drop

the control with a release of the left mouse button, then repeat with another click and drag. This time

you are dragging a copy of the control you just released.

You need to try this for yourself - try an place six to ten Label controls, one after another in quick succession.

Here is my result, it took very little time, but then I have just dropped the individual controls randomly - anywhere.

Check this out :-

======================================================

Now I will marquee select them all with the mouse click and drag an area, note

the wire framework completely around the ten items (controls) :-

=======================================================

And if we now press the 'Delete' key the whole bunch of ten controls disappear !

Like this :-

======================================================

As well as me warning you to be careful what you do with your selection and subsequent

key presses, there is 'Good News' ;-0)

You may like to know, as well as try out thoroughly for yourself, the "Ctrl +Z" and "Ctrl + Y" key

press operations.

They work well, and at least allow us a 'way out' when we do something silly, or unexpected.

More on Duplication of Controls :

So far we have seen how one control in focus can be copied by using the 'Ctrl' key and a mouse drag.

Well, if we multiple select, then do the same copying procedure, we get copies of multiple controls.

The following image should be obvious and understandable from this point of view - check it out :-

======================================================

You should be able to work out which Label control was placed first, and then which were grouped

together, and also which group came after which, etc., etc., etc..

Now then, lets clear up again.

Marquee select the nine new Label controls, then press the 'Delete' key.

See below just before I press delete :-

===================================================================

Okay then guys, I think you need to go away and do some experimenting and testing.

Build up those skills you will require to become a 'Master' at the use of the report 'Designer'.

Remember, "the 'Designer' is your friend !".

PHH - 13th. & 14th. January 2018.


another sample - using Business objects ...

This is a VERY interesting topic folder, for a number of reasons.

Firstly because it is really quite easy to do, from a Collection of business class objects.

And secondly, and possibly most importantly, because it provides a modern solution to VO / Vulcan DBF

guys as a way to make DBF data into WPF reports.

Its not a total solution for DBF guys as the reports are not built directly on the database. But it is an easy way

to create and provide some good 'solid' reports that look good.

Also, it looks as though it is as easy to code the reports manually, as well as with the report designer, once

we have defined our business class, and hard coded some data.

Eventually the solution / project will make the Collection of business objects after reading the database, DBFs or SQL.

Okay then, lets start simple, and see what is easily achieved :-

[ .. note the DoB need some work to improve the formatting .. ]

============================================================================================================================================

The details in the Report 'Designer' are shown below - these were made by me and using the Wizard :-

===========================================================================================================================================================

Okay then folks, what does the 'Person' class look like in its definition ?

Checkout below :-

=========================================================================================================

The above X# code is as simple as we can make it really, and later we will improve upon this basic of

definitions.

Making a small Collection of Person objects :

======================================================================================================

============================================================================================================================================

Upgrading the Person class and extending the data values :

Okay guys, lets try and improve the report shown above.

Lets add a couple more report fields (actually class Properties) as well as improving the DoB display.

==========================

===========================================================================================================================

=============================================================================================================

================================================================================================================

=======================================================================================================================

==================================================================================================================================

Lets see what the 'RE' (Report Explorer') pane looks like, with some of the Properties pane as well - see below :-

==========================================================================

With the Details folder collapsed we see the RE pane looking like this :-

=============================================

=================================================================

======================================================================================

============================================================================================================

Now we can take a fuller look at the expanded RE pane - check all the details shown below :-

=============================================

==========================================================================

============================================================================================================================================

==========================================

=======================================================================================================================================

===========================================================================

===================================================================================================

=====================================================

===============================================

PHH - 8th. & 9th. January 2018.


Navigation: 'Printing and Printers', and Report generation too ... > Third Party report generators for .NET > DevExpress Reporting > another sample - using Business objects ... >

Return to chapter overview

Using the Wizard to group Person data ...

PHH - 10th. January 2018.


Grouping the Person data ...

Okay then guys, we seem to be getting somewhere with this DevExpress reporting.

Now that we can successfully create a very 'standard' report, lets try something a bit fancier.

Let us try a 'Grouped' report, say in our case of the Persons 'collection' one where we group people

(or Persons) living in the same city.

Before we see what is required to make such a report, lets see the final Preview image - check below :-

==========================================================================================================================================

In the above report image we have needed to change the original data so that each Person is either

resident in "London" or "Birmingham".

Here is my new data set :-

================================================================================================================================

The details shown in the Designer are a result of me using the Wizard :-

[ .. I just edited a couple of small details when the Wizard had done its part .. ]

=======================================================================================================================================================

In the above details I changes 'DoB' to 'Birth', as well as providing the title information as shown above.

Oh! and as well, I changed the text formatting of the DateTime so that it was 'dd/mm/yy' as shown here :-

====================================================================

Here below is a expanded view, using the 'Ctrl' key with the mouse wheel :-

==================================================================================================================================================================

In the above image it is tempting initially, to think that the missing 't' from 'Street' for Danny's entry (data line)

has been caused by the enlargement - BUT - it is not the longest line in the data set, so it is simply that I made a typing error

while creating the data in code - check below :-

==========================================================================================================

Now then, lets save the report for use later - here is what the screen details show :-

===================================================================================================================================================================

Hopefully, we will use this report file again quite soon, when we try to use the 'DocumentPreview'

control with a string which points to the file path of the report file.

Here is a scaled up preview output, with the 'Side Street' text corrected :-

=======================================================================================

Also, another way to change the display size is to use the slider control at bottom right of the screen/window :-

Looking back at the data, and the Age column, notice in the detail of the image shown, the column is still in order

of highest to lowest - within each grouping.

Obviously, our LINQ code is still working well ;-0)

PHH - 10th. January 2018.


Navigation: 'Printing and Printers', and Report generation too ... > Third Party report generators for .NET > DevExpress Reporting > another sample - using Business objects ... > Grouping the Person data ... >

Return to chapter overview

working code and script

=============================================================================

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.Navigation

USING System.Windows.Shapes

BEGIN NAMESPACE WpfDXgoGO

/// <summary>

/// Interaction logic for WPFWindow1.xaml

/// </summary>

PUBLIC PARTIAL CLASS WPFWindow1 INHERIT Window

  PUBLIC CONSTRUCTOR()

       InitializeComponent()

RETURN

end class

public class Person

  public property Firstname as string auto

  public property Lastname as string auto

  public property Address as string auto

  public property City as string auto

  public property Age as int auto

  public property DoB as DateTime auto

  public constructor()

  return

  public constructor(cF as string, cL as string, cA as string, cC as string, dtD as DateTime)

      self:Firstname := cF

      self:Lastname := cL

      self:Address := cA

      self:City := cC

      self:DoB := dtD

      var today := DateTime.Today

      //--- rough guess / estimate for now ---

      self:Age := today.Year - self:DoB.Year

      //--- a better evaluation ! ---

      if (today.Month < self:DoB.Month || ;

                           (today.Month == self:DoB.Month && today.Day < self:DoB.Day))

          self:Age --

      endif

      //--- Leap years need still to be considered ! ---

  return

  public static method GetPersons() as List<Person>

      var aList := List<Person>{}

       aList:add(Person{"Micky","Mouse","39 Sunset Boulevard","Orlando",DateTime{2002,03,04}})

       aList:add(Person{"Danny","Drinkwater","5, Side Stree","Newport",DateTime{1996,06,14}})

       aList:add(Person{"Eva","Evangelista","12, Blind Alley","Newcastle",DateTime{2013,02,24}})

       aList:add(Person{"Grandpa","Guggenheim","137 Main Street","Southampton",DateTime{1973,09,22}})

       aList:add(Person{"Sally","Slack","11, Front Street","Birmingham",DateTime{1995,05,10}})

       aList:add(Person{"Danielle","Boomaker","9, Plane Tree Avenue","Manchester",DateTime{1997,08,23}})

       aList:add(Person{"Edward","Underwood","10, Baker Street","London",DateTime{1975,07,11}})

       aList:add(Person{"Laura","Hepburn","39, Highrise Flats","Wokingham",DateTime{1985,01,04}})

  return aList

end class

END NAMESPACE

================================================================================================

<Window

      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

      xmlns:dxrud="http://schemas.devexpress.com/winfx/2008/xaml/reports/userdesigner"

      x:Class="WpfDXgoGO.WPFWindow1"

      Title="WPFWindow1" Height="600" Width="700">

  <Grid>

      <dxrud:ReportDesigner Margin="10,10,0,10"/>

  </Grid>

</Window>

================================================================================================

  public static method GetPersons() as List<Person>

      var aList := List<Person>{}

       aList:add(Person{"Micky","Mouse","39 Sunset Boulevard","London",DateTime{2002,03,04}})

       aList:add(Person{"Danny","Drinkwater","5, Side Street","Birmingham",DateTime{1996,06,14}})

       aList:add(Person{"Eva","Evangelista","12, Blind Alley","London",DateTime{2013,02,24}})

       aList:add(Person{"Grandpa","Guggenheim","137 Main Street","Birmingham",DateTime{1973,09,22}})

       aList:add(Person{"Sally","Slack","11, Front Street","Birmingham",DateTime{1995,05,10}})

       aList:add(Person{"Danielle","Boomaker","9, Plane Tree Avenue","London",DateTime{1997,08,23}})

       aList:add(Person{"Edward","Underwood","10, Baker Street","London",DateTime{1975,07,11}})

       aList:add(Person{"Laura","Hepburn","39, Highrise Flats","Birmingham",DateTime{1985,01,04}})

  return aList

  public static method GetPersonsLastA2Z() as List<Person>

      var zList := Person.GetPersons()

      var hList := from item in zList ;

                  orderby item.Lastname ;

                  select item

      var aList := hList.ToList<Person>()

  return aList

  public static method GetPersonsAge921() as List<Person>

      var zList := Person.GetPersons()

      var hList := from item in zList ;

                  orderby item.Age descending ;

                  select item

      var aList := hList.ToList<Person>()

return aList

================================================================================================

PHH - 9th. January 2018.


Grouping using a 'part' hand coded approach ...

Well then guys, lets try something a little different.

We are going to reuse an existing report file which was previously created using the Designer 'tool' and

the Wizard built within it.

Oh! and the format of the file to be used needs to be of a 'print' format.

By the above I mean to tell you that there are two file formats - one the printed report really, this is a '.PRNX' format,

the other is of secondary filename '.repx' and is really the plans and blueprint for the whole report, including element

layout etc., etc..

Here is the file I hope to re-use it is first in the list :-

========================================================================================================================================================

And for your knowledge and information here are the 'blueprint' files :-

==================================================================================================================================================

The 'Designer' control accepts '.REPX' files, and the DocumentPreview control those of type '.PRNX'.

I am going to start a new project (from the current solution I have) in order to do this.

Below is how it looks when completed :-

========================================================

Notice that in the above image the new project is set as the start-up project.

Lets do this the 'back-to-front' way of doing things - lets start with the results of our efforts.

Checkout this 'Grouped' report - see the City name in the grey bar :-

====================================================================================================================================================================

You may also wish to notice the order of the data lines, check the Age column to see that they are

in order of oldest to youngest - numerical vales high to low.

Note that the control on the WPF form is of type 'DocumentPreviewControl' which is part of the DX

(DevExpress) toolset.

We can check this out by looking at the XAML script, as short as it is, only a line or two -check below :-

====================================================================================================

Notice above that we are going to use the line 7 'loaded' event for the 'Window'.

Once the Window loads we will cause the display of an existing report file to be loaded and shown on the control

surface.

So now we need to checkout the X# code to see what is required there :-

==================================================================================================================================================

Please note line 18, I had to find out for myself that this was missing from the DevExpress documentation.

The following image shows which assembly was missing from the References section, this was to

go along with the namespace shown on line 18.

=========================================================

Out of interest, when we slide from the toolbox a element of type 'DocumentPreviewControl', all

the required references are added for us, except in this case that shown highlighted. I need to report

this back to the DX guys.

Okay then folks, lets see what XSharp code is required to be able to display out report file.

Note that below the line 27 commented code was just the first filepath to be tried, and it worked :-

====================================================================================================================================================

The file types required for this code are those of type '.PRNX' - which are printing files. Not report 'blueprint' files

of type '.REPX' - which are effectively plan or blueprint files, needed by the Designer control.

Here is what the top left of the Previewer looks like :-

====================================

Funny ! - if we can't edit / change the report then why do we have a Save option ?

Is it so we can force reports from source folder and save a copy to a local / target folder ?

We need to answer this question too.

Answer to Question !!! ??? :

Oh! - I have found a small entry in the DX documentation which explains this.

Apparently in the previewed report - static type or other it would seem - it is possible to make a text

box element editable.

The text box being edited is not bound to any back-end data, just that it can be edited for text content,

and then saved so it retains the same content when the newly saved report is reopened.

This means for example, that a 'Willy Wonker golden ticket' can be supplied with the winners name.

I will try this out to show you what I mean - here is the working result :-

=====================================================================================================================================================================

Yep! - we have a 'static' report which will allow me to enter (and change) a winner's name - see above and

my misspelling of 'Little' ;-0)

In order for the text content of the Label control to stay for the next preview etc., the changed control, and

report needs to be saved - BUT - make sure you have clicked outside of the control first, so that the changes

get registered by the system - I got caught out when I first tried this editing facility.

I will show more of the details of this in a separate sub-folder, so look out for it.

=============================================================================

Moving on ...............

Before I leave this subsection, we need to discuss just what use this method of approach is to us.

** Note *** I am referring back to the way we can use a filepath string to point to and open a report.

Well quite obviously we could have a local user 'source' folder which fills a drop down box with all

the possible report files in this sub-folder. When the user selects one this report is shown on the viewer

tool on the WPF form.

In real time the system could update these available reports, removing and adding ones as the day goes

by and things change etc..

This way the user consuming the reports does not need to have the data available which made the reports.

I should imagine we can do exactly the same with WinForms as well.

Lets move to another sub-section .............

See you there.

PHH - 10th. January 2018.


Navigation: 'Printing and Printers', and Report generation too ... > Third Party report generators for .NET > DevExpress Reporting > another sample - using Business objects ... > Grouping using a 'part' hand coded approach ... >

Return to chapter overview

working script and code ...

================================================================================

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.Navigation

using System.Windows.Shapes

using DevExpress.Xpf.Printing

using DevExpress.Xpf.DocumentViewer

using DevExpress.XtraReports.UI

BEGIN NAMESPACE WpfDXgroupCoded

  PUBLIC PARTIAL CLASS WPFWindow1 INHERIT System.Windows.Window

      PUBLIC CONSTRUCTOR()

           InitializeComponent()

      return

      private method Window_Loaded(sender as object, e as System.Windows.RoutedEventArgs) as void

          local report := XtraReport{} as XtraReport

          //self.documentPreview1.DocumentSource := "C:\Users\the\Documents\DXsamplesMine\My_Report_Age921.prnx"

          self.documentPreview1.DocumentSource := "C:\Users\the\Documents\DXsamplesMine\TestTest13.prnx"

           report.CreateDocument()

      return

  end class

END NAMESPACE

================================================================================

<Window

      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

      xmlns:dxp="http://schemas.devexpress.com/winfx/2008/xaml/printing"

      x:Class="WpfDXgroupCoded.WPFWindow1"

      Title="DX Group Coded Trial :" Height="650" Width="1300"

      Background="Beige" Loaded="Window_Loaded">

  <Grid>

      <dxp:DocumentPreviewControl Margin="10,10,30,0"

                                  Name="documentPreview1"/>

  </Grid>

</Window>

================================================================================

PHH - 10th. January 2018.


Grouping using a 'full' hand coded approach ...

Okay the guys, we have managed to get a reported 'output' on screen with direct use of a 'Designer'

or Wizard.

Okay - yes, I realise that I had originally made the '.REPX' file that way and only when I ran the preview

did I get a printer file format file like '.PRNX'.

So wouldn't it be nice if we could do the whole 'thing' within our X# code ?

Then we could write code to make and show reports whenever we wished to do so, and without need

for the user to have reporting and designing skills.

I obviously have a loot of research and testing to do ;-0) Wonder where Nick is ?

PHH - 10th. January 2018.


Hand / hard coded reports ...

Reports can be created (done) entirely in code, and without a Designer tool at all.

There are good reasons available for doing both the handed crafted ones, as well as those designed in real time

using the in-built DX designer tool.

We are quite spoilt for choice it would seem ;-0)

==================================================================================

Here is what an experienced VO / .NET coder had to say about hand coded reports for DevExpress software.

Read this carefully :-

"All my reports are predefined at the moment (rather than letting users loose on the end-user designer),

and all work with 'object' type datasources, so I build a collection of custom business objects in memory

and print from that.

One of the really nice things is being able to connect to collections instead of 'db' datasources... means

you can do all the difficult processing and organisation in "normal" code, and then simply pump the

regurgitated results through to the reporting engine.

Makes report design very simple."

Nick Friend - January 2018.

Because Nick is an experienced .NET guy, when he says "you can do all the difficult processing and

organisation in 'normal' code", he actually means he is using LINQ on his collections.

This is VERY Good News for the xBased DFB guys :

So why all the excitement for VO / Vulcan guys ?

Simply that it is relatively easy to write code to read DBF based data and make from this some well crafted

business objects, and hence a collection of such business objects.

Obviously it is very flexible, from the developers point of view, to be able to define and build in code such data

objects. We have all of the LINQ power and flexibility to apply to our business objects. So we can therefore

do virtually anything with DBF data, if we so wish.

Once we have collections we can easily pass these on to the DX reporting engine. We will see more of this

quite soon. BUT - from my standpoint I will not start with the DBFs as that is best left to those who regularly

use them. I will create collections from my own hand crafted data, and then use these collections to pass

through to the reporting engine.

Keep your fingers crossed ........

==============================================================================

PHH - 8th. January 2018.


First simple attempt, and success ...

Okay guys, let move forward and acquire a couple more VERY useful skills.

We will hand code a simple report, just to shown how its done.

Once we have the report coded we will add the code to show it at runtime.

We will also try to save it as a REPX file, for use and consumption at a subsequent date.

Later on in the notes, we will see how to call back any reports, including those made by the Designer.

First of all lets look at what our simple report looks like - checkout below :-

===============================================================================================================================

Notice above the general 'look' of the Preview window - it is very simple, but has save and print menu options.

Now then, what does the code look like which produces the simple message to Nick ?

We are going to code a class based on the 'XtraReport' class in the DX reporting framework.

Study all the following details :-

[ .. notice the sections - header, footer, details .. ]

===============================================================================================

If you study the above image thoroughly, you will see that we have done little except add a textbox type of

control (called in DX a label) and add some simple text to it, as well as define font information.

Oh! and we set colours to fore and background.

So lets leave the code for now and see how we go about displaying what we have coded.

Here below is the start of my test WPF app main window - the action takes place in the event handler method on line 69.

Also note that we display the report twice, the second time starting on line 73 through 76 :-

==================================================================================================================================

Here is the less 'simple' look of the 'DocumentPreviewControl' used on line 74 - the earlier option was much simple.

Either that or the DX code 'under the hood' used a similar control with property settings to simplify the menu are - we

will have to investigate.

Whatever, we at least can show / display our handmade Report.

I await Nick's reply ..............

Going further - seeing Stored files of type '.REPX' :

Okay then folks, it is one thing to want to hand code a new report for ourselves, but quite another

and different task to take an existing report on file, and redisplay it on screen.

This report file (of type '.REPX') could have come from many external places - from other computers and

PC systems. All we need to know is that they are in a folder somewhere which is accessible to our running app.

So how do we display a '.repx' file ?

Here below is some test code which is working for me currently (it needs some tidying) :-

[ .. line 73 is the line to study .. ]

===============================================================================================================================================

Okay then, we know the code needs tidying, BUT, the method 'FromFile() looks to be a STATIC method which returns a Record object

from the string which is the filepath.

We can now look at the following code which is what I have just tidied up and tested.

Because we are using external data / details of other reports, we don't need to refer to 'MyReport' any more.

So 'FromFile()' is seen to be a static method of the 'XtraReport' class.

Study the new details thoroughly :-

=========================================================================================================================

So now that we have some simplified and tidied code for retrieving the Report, lets now look

at how the retrieved report can be displayed :-

===========================================================================================

The above code at line 227 is one easy way, all we need is the Report object.

And another way shows a more complex menu bar area.

The code shown below does need to refer to Class 'MyReport' but only in as much that it is

accessing the STATIC method 'PublishReport()'.

Check this out fully :-

[ .. note I have tried two files and both work okay .. ]

=========================================================================================================================================================

Okay folks, we seem to be getting somewhere I feel.

Time to move on and learn some more 'fun' stuff in the next sub-section.

See you there ............

PHH - 23rd. January & 8th. February 2018.


Placing Label items on the screen page ...

Well guys, this is where it get a bit messy to my mind.

However, we can try and have some fun at the same time as learning a bunch of 'stuff'.

At least we can have comfort in the fact that we can give up on the idea of hand/hard coded reports,

altogether and restrict ourselves to getting the best from the Designer tool features and facilities.

C# to our aid ! :

I am afraid that sometimes when we wish to learn, and look elsewhere for samples to copy and adapt,

we need to resort to looking at C# code. That's just the way life is ;-0)

I have always said that an effective xBase and X# coder will have the ability to read and write C#. So lets

take this opportunity to brush up on our C# skills, and at the same time learn how to place graphical

toolbox elements on the report page.

I have used my experimental solution to create a C# project which is a ClassLibrary type.

Check this out - three X# projects and one other of the C# variety :-

=========================================================

The reason I have done this is because we need to use a template in the C# folders of the

template dialog window, in order to create a Report coded entry (item).

At the moment there is no such template in the X# area. This may change with time.

In the following image notice the two 'XtraReport's at the bottom of the ClassLibrary listing

in the SE pane :-

=========================================================

We are going to explore the code made automatically by the DX Report Wizard in order to

find out how to place graphical elements, we will limit ourselves to using Label items at first.

Let us find within the code, as simple an example of a Label definition as possible.

How about this ? :-

Okay then, that looks hopeful, and we need to compare this with what I am currently doing in my X# code

to see what I have been missing out.

================================================================================

Here below is the result on my X# code :-

[ .. the dotted lines shown are margins on the Page, not a header (I think) .. ]

===========================================================================================================================================

As you can see it works okay, but has had no location or size properties set, we must be just using the

default values I suspect.

We need to experiment a little I feel.

Let us try and move both down and along, the underlined text, by copying one aspect of the C# code.

Lets set the location by giving a value to the property 'LocationFloat' - check below :-

============================================================

Notice that in order to get this to work I had to change the 'F' in the C# code to 'S' in my X# code.

'S' is for single value float - I need to ask Robert about this!

But look at the results, just as we wished, the underlined text has moved both down and along to the right :-

==========================================================================================================

Before we move on to find out more about placement etc., lets see the whole code section for this Label :-

=================================================================================

Yes, I know, I could have (and will) simplified the lines on line 247 through 249, but I was

struggling to get the syntax correct.

==========================================================================================

Moving on a pace .............

Okay folks, I know, this looks like we could over-print one element with the other - lets try it.

=======================================================================================================

Arrrh! - in the above image we see that as we move down (and across) the black text label, the coloured one

also seems to move.

Here is the code I used for the black text label :-

=============================================================================================

So to confirm my 'hunch' lets increase the numbers some more :-

=============================================================================================================================================

Yes, we are certainly 'pushing' Nick's box down the page, as it is displayed in the Previewer.

Here is the code and values used for the above image display :-

===============================================================================================

You see we are up to coordinates of 199 in both x and y directions.

These coordinates are like Microsoft's "Canvas" coordinates, with top left being 0,0 and this is NOT

a very popular choice with developers, never was and never will be. Workable yes.

Now then, we can easily make a change in our code to bring about 'overlapping' - we just need to make

sure that we add the Label elements into the same Control 'space'.

Lets try adding all the Labels into the 'Detail' section :-

============================================================================================

And straight away we can see a difference, and that possibly now we can do some useful comparisons.

See in the following image how Nick's label has returned to position 0,0 within the Detail area / space :-

=============================================================================================================================================

I would guess that this is because no location values were supplied in my code.

When I say 'location values' I mean coordinates really - these come as Point objects which are two float values.

A 'Point' is a pair of floats.

Lets now look at some code and results where we have found out more useful information.

Check these details :-

============================================================

As you can see we have declared five Label type objects which we can instantiate and add to

the Detail section of our Report.

Here below we see the code to instantiate Labels 2 through 5 :-

==============================================================================================

Ignore line 285, this does NOT do what I thought it did, we can't set the 'Z' index value, which would

normally determine which Label element was shown on top or beneath, when overlapping is to occur.

Index appears to be a read only property, set by other means.

It would also appear that using 'BringToFront()' directly on the Label object does not seem to me to work

as I would wish. We will see another (successful) way soon.

Look below at the two Labels which do overlap, as well as the placement which is determined by the

'LocationFloat' property :-

=====================================================================================================================================

It would appear that unless we specifically handle the display order, that the first element to be placed

in the Controls collection stays on the top.

Lets do some more experimenting and exploring.

The above image was created using code where the 'Hello...' Label was added first to the Controls collection.

If we now change this and add the 'Hello...' Label last we see two things - lets try it first :-

=================================================================================================

The 'Hello...' Label is now in a very different place / position.

It was added last and because we have not specified a 'LocationFloat' property the reporting system places it last,

after the 'Label5' element.

Check below to see the visible results :-

================================================================================================================================

Okay then guys, we are getting closer to some useful understanding.

Lets now try to place the 'Hello...' Label at the earliest vertical place in the Detail band, and then also add

the element first into the Controls collection - here is the code, lines 243 and 245 are those to study in particular :-

=============================================================================================================

And when we view the results below we see 'Hi there...' on the top of the visual layers :-

===========================================================================================================================

Okay then folks, what about keeping all the locations as they are, and then try to change the Indexing by

either sending one element to the back, or bringing another to the front.

We need to be careful to select the right element for the right 'action' as such.

Check this out below - this is NOT the way to do it :-

=============================================================================================================

Try the above code for yourself line 241 does not work for me.

There has to be another way to do this - but if you study the following image you will see that we have made the

desired effect :-

============================================================================================================================

Okay then guys, what did we do to reverse our display order in the overlapping section ?

If we see line 291 below, this is what seems to do the trick for me.

Note that we are using the indexing access syntax to the collection of Controls :-

=================================================================================================

It is not easy to show this sort of behaviour in static eNote format. You do need to go and do some experimenting

of your own, so as to get a 'feel' for what is going on.

I do hope I have made this clear ? ;-0)

Lets move on, see you in the next section .................

PHH - 9th. & 11th. February 2018.


Navigation: 'Printing and Printers', and Report generation too ... > Third Party report generators for .NET > DevExpress Reporting > Hand / hard coded reports ... > Placing Label items on the screen page ... >

Return to chapter overview

General Report layout ...

Here is a very useful link :-

https://documentation.devexpress.com/XtraReports/2587/Creating-Reports-in-Visual-Studio/Getting-Started-with-DevExpress-Reporting/Quick-Tour-of-Visual-Studio-Report-Designer/Introduction-to-Banded-Reports

It shows us some helpful graphical layouts of some possible report displays.

Remember, that the anatomy or structure of the Reports is by definition a 'Banded' type.

Apparently Microsoft wished things this way - so all MUST follow !?

=============================================================================================================================================================================================================

Well so far this seems to make good sense.

Lets read on :-

=====================================================================================================================

And read on further - the seems to be the 'key' to banding :-

========================================================================================================================

The none WYSIWYG report structure does make sense, with the data 'repeated' rows being effectively collapsed into one,

or a few lines within Detail sections (bands).

Here is an view in the Designer of an empty Report :-

=====================================================================================================================================================

And finally, a bit more simple explanation :-

==================================================================================================================================================

The above information is clear, and very specific.

Dynamic, data bound displaying within the Detail section, and no place else (not any other bands).

Static information in all other Bands.

And I think we need to try to see if nothing can be displayed in the margin bands. I will explore.

Oh! - guys, what a surprise !!!

Check out the following test code, along with its Reporting display :-

======================================================================================================================================

Well I never ! - What is the point of a margin if we can place elements within it - especially since

I strongly suspect that printers may not be able to print some of the content, closest to the page / paper edge.

Again we need to experiment .................

Oh! folks, just as I thought, the printed copy show clearly that the top half of the "my Margin Label ....." is

missing on the printed page from my laser.

I must admit I find this a bit strange - another good reason to stick with using Wizards ;-0)

I will try and take a photograph of the results as described above - check this out :-

==========================================================================================================================

Yes, it is clear to see that print information has been lost.

I will need to think about this, as well as report back to the DevExpress support Team.

When I wrote my Printing Class for VO (through to Vulcan.NET) the basic maths done by Alwyn (Jennings-Bramly) made

sure that the unprintable area around the edge of the paper for any given printer was unused, or left free from attempted

printing. I thought that this approach was a MUST for any serious printing package !?

However, this does NOT make the DX WPF Framework unusable - we just need to appreciate some of its limitations,

as well as it MANY great strengths.

The way I see things is, why let a developer / user attempt to print in areas which are not usable by the printer?

But then I seem to see things simply and clearly, I hope.

Lets move on ..............

PHH - 9th. & 11th. February 2018.


Navigation: 'Printing and Printers', and Report generation too ... > Third Party report generators for .NET > DevExpress Reporting > Hand / hard coded reports ... > Placing Label items on the screen page ... > General Report layout ... >

Return to chapter overview

Page coordinate system - explained ...

*** CARE *** this is NOT a simple task !!!

There are a number of complex issues involved, and values can change depending on a whole

host of other variables - to do with paper size, orientation and printer type etc., etc..

This is one reason that I wrote and developed my own Printer class.

======================================================================

Lets ask ourselves a simple question, ... or three.

How long is a Page, and how Wide is it as well ?

When is a Page different to another Page ?

Does the coordinate values of either/both of these (length and width) stay the same ?

And on a more difficult note, how deep is a Band ? (Depth being height.)

What if we try to set the total depth of all of the Bands to a value exceeding the Page length (depth) ?

What if we make a control wider than the Page width ?

If we use the Wizard we probably never need to worry about any of these!

I think we could have some 'fun' experimenting in order to answer some / most of these questions.

But get me straight, I fully intend to use the Wizard(s) and note hand code Reports !!!

There are a few properties in the eNotes from DX about page sizes and other related 'stuff', Here

below is one of them :-

==========================================================================================

And here is another - which we will use in just a few moments :-

=====================================================================================

I am using a pretty standard laser printer with A4 paper for my testing.

However, when I did the following code test I had not checked whether the paper was set as a default of

'legal' which is what some DevExpress settings have proved to be while using the Designer control. I suggest

you check the 'Legal' paper size, it caught me out just there now (again).

I need to check - meanwhile study the following see below.

Also remember that the 'self' refers to an XtraReport1 object :-

================================================================================================================

The above seems more than I got while trying to sum up the height of all of the Bands I chose ???

However, the next vale found below is only '2' different to what I calculated - check this :-

================================================================================================================

And cross-check with my summation code :-

======================================================================================================================

Okay, interesting, so for me with a pretty standard A4 laser printer, we have a page of 1,100 by 850.

The problem is however, that because we are using the 'Band's report anatomy, we can't address all parts of the Page

directly - the coordinate system seems to be set at 0,0 for the top left hand side of each of the Bands that we use

in the Report.

AND - worse than that, the coordinates of one Band is effected by the Height property of other Bands - yes, I did warn you,

it can get a bit complex. Not rocket science - BUT - complex indeed.

Here is an idea for a good experiment.

Let us try to place a small Label in each Band starting at 0,0 and see if this helps our understanding.

We are therefore assuming that each and every 'Band' has a position (or location) of ' 0,0 '.

Okay folks, experiment is over and done I feel - see the results below :-

=========================================================================

In the above image we have only provided Labels with a ' 0, 0 ' coordinate pair - that is within each band

and I have simply adapted the previously existing test code - so we have commented lines to make this

work for us.

Check below for the Bottom Margin test code :-

===========================================================================================================

Oh! - probably I should have left out the line setting the 'Padding' property - check above at line 291.

And now without any padding we have the Page Footer code :-

============================================================================================

Time to move on ...........

However, if we were to wish to create a simple document, or printed / displayed page, then we would

really need a Class type which presented a one Band structure - effectively offering the whole Page as

a target for our coordinate system.

If we do things this way then we have only one ' 0, 0 ' point on the graphical surface.

Maybe the correctly chosen preview class with accept a document as an initiating parameter !?

Check another sub-section to follow this idea through.

PHH - 10th. & 12th. February 2018.


Navigation: 'Printing and Printers', and Report generation too ... > Third Party report generators for .NET > DevExpress Reporting > Hand / hard coded reports ... > Placing Label items on the screen page ... >

Return to chapter overview

Locating elements on the graphical 'Page' ...

Okay folks, like it or not, Wizard use or hand coding, we all need to know enough about the areas

or regions of the report page that mean we can understand what is being done.

Obviously, if we are to hard code a Report class object we MUST know a fair amount of detail.

However, my belief is that because printing and reporting is such a complex business, we do need

quite an in depth knowledge of both the graphical page elements making up the report, as well as

the page areas/regions which accept smaller graphical elemets.

By this I mean, we can know about Label items which are to be placed within the report, BUT, we

need to know sufficient detailed information about Headers and Footers, and Pages and the Report

itself.

The best way to get a 'feel' for what is going on is to try and code up a test / trial sample report.

I have just been through such a process of experimentation, and learned a lot in doing so.

Summary of my experiences is that nothing is difficult or complicated to understand - BUT - there

is MUCH to learn and the sample report code is long and complex. Some would describe this as

fiddly and/or messy.

Myself, it has totally convinced me to leave all the 'deep down' fiddly stuff to a well designed and

thoroughly tested Wizard ;-0)

Before we get into the finer detail of what code I have written, and for why, lets see a layout sort

of schematic of what I produced at Preview time - checkout the following :-

===========================================================

Oh! - good - we can do all this above by X# code alone ;-0)

All will be revealed some enough!

Why 'MY', 'My', and 'my' ? :

Before we get deep into explanations of 'this and that', let me tell you why I have used 'my' so many times in both

the test code, as well as also the text properties displayed.

No, I am not a 'naff' of self-centred person, I am simply doing the 'my' thing so that both me and you can more easily

tell where the object names have arisen - any Framework is NOT very likely to use the term 'my'.

So in future in this section when you see a 'my' think of it coming from the code I have typed - test code.

"Made by us effectively."

In light of the above, lets now read through the start of the test Report class, and see the class properties :-

================================================================================================

Note that in the code above I have dropped the 'my' on all the required Label objects.

So it would appear that 'my' Report has seven bands within it.

These are two margins, two headers, two footers and a single detail in between them.

This is about as simple as we can make things if we are going to create a standard or realistic

report structure - anything else would be too simple from which to learn much.

If you look back to the top of this sub-section the graphical image shows what I have just explained, PLUS, it

shows five separate Label objects within the Detail section.

Here are the Detail Labels :-

============================================================

In fact, because we add each Band separately into the collection, we can comment out the Detail

Band and see the Report page without these details. So the five Labels should 'disappear'.

Here is the code :-

================================================================

And now the runtime result :-

=====================================================

So the above image was generated with the six other bands, and not including

the 'Detail' band.

Adding the 'Band's - into the collection :

You may have realised by now that the Report is a collection of 'Band's and each Band is

also a collection of Labels (and possibly other types of element).

For what we are about to do in our test sample, each Band will have a 'collection' of Labels.

There are two alternative syntaxes for placing objects into the collection - one used at instantiation,

the other used repeatedly after instantiation has taken place. Lets take some time to study these.

From an earlier and simpler test :- [ .. line 226 is the one to study .. ]

================================================================================================

But when we have many 'Bands' to display on the Report page it is probably best / easiest to code as follows :-

========================================================================

One benefit of the above syntax is that we can comment out for testing, one or many of the Bands

separately. This is how we tested the 'removal' of the Detail label objects - above.

Moving on ........................ lets checkout each Band details ..................

Studying the detail code for each 'Band' in turn :

Lets start with the Top and Bottom margin sections.

TopMargin :

=================================================================================================================

BottomMargin :

====================================================================================================================

And now the Report Header and Footer :

============================================================================================================

=================================================================================================================

And now the Page Header and Footer :

===================================================================================

===================================================================================

Summary - collections :

So the Report has a property 'Bands' which is a collection of Bands, and each Band has

a property called 'Controls' which is a collection elements like Labels - any control which

can be placed within the Band.

A 'Control' is basically a graphical element.

============================================================

==========================================================

Now lets deal with changing (enlarging) the Top and Bottom margins :-

Lets change the depth / height to 180.

==================================================================================================================

We can see below that the top/bottom margins are now MUCH increased :-

====================================================

PHH - 9th. & 10th. February 2018.


Navigation: 'Printing and Printers', and Report generation too ... > Third Party report generators for .NET > DevExpress Reporting > Hand / hard coded reports ... > Placing Label items on the screen page ... > Locating elements on the graphical 'Page' ... >

Return to chapter overview

C# code on which we based the X# sample ...

We need to look at the 'XtraReport1' class specification in the documentation.

=============================================================================================================================================

=============================================================================================================

=================================================================================================================

====================================================================================================================

===================================================================================================================

======================================================================================================

==============================================================================================================================

PHH - 10th. February 2018.


Navigation: 'Printing and Printers', and Report generation too ... > Third Party report generators for .NET > DevExpress Reporting > Hand / hard coded reports ... >

Return to chapter overview

Second and better attempt, with success ...

PHH - 23rd. January 2018.


Getting back REPX file data from code ...

=========================================================================================

=====================================================================================================

PHH - 8th. February 2018.


Data sources - varied syntaxes and settings ...

Okay the guys, we had better roll up our sleeves and grit our teeth, etc., etc..

Yep! - this topic has been an 'old chestnut' for MANY years.

In principle it is easy, we just need to find a few lines of .NET code to allow us to connect to

the chosen, or required, back-end database - or these days a data source, as our data may

well be an in-memory data object.

The problem simply seems to be that it is not always easy to find examples of suitable syntax

which is close enough to what we require, and which is easy to copy and adapt.

Also, when I comes to SQL examples, the connection string details are so individual to the user

and the PC/machine being used, that it is difficult (nigh on impossible) to supply a meaningful

sample to copy.

For this reason I suspect, DevExpress themselves use a easy copyable Access data file to use

with their sample to do a data bound report.

Below is code from their C# sample and we can see the string in the parameters input the reference

to the "nwind.mdb" data file. [ .. I think this is the old 'North Wind' data file from SQL years back .. ]

=====================================================================================================================================================

For our first trials of X# with DX bound reports I will use the same file.

Here below is where it is located after the automatic download from DevExpress :-

========================================================================================================================

And here is what the C# sample produces, after I have de-sized it quite a bit with my mouse wheel :-

=============================================================================================================

And the detail of the top of page one looks like the following :-

==========================================================================================================

To see more on this test sample / application, go a couple of sub-sections below for 'Access97 ...... '.

Now I will go and translate the C# code to X# and see if I can get a similar result.

PHH - 24th. January 2018.


Navigation: 'Printing and Printers', and Report generation too ... > Third Party report generators for .NET > DevExpress Reporting > Hand / hard coded reports ... > Data sources - varied syntaxes and settings ... >

Return to chapter overview

SQL Server

PHH - 24th. January 2018.


Navigation: 'Printing and Printers', and Report generation too ... > Third Party report generators for .NET > DevExpress Reporting > Hand / hard coded reports ... > Data sources - varied syntaxes and settings ... >

Return to chapter overview

In-Memory, business objects 'Collection'.

PHH - 24th. January 2018.


Navigation: 'Printing and Printers', and Report generation too ... > Third Party report generators for .NET > DevExpress Reporting > Hand / hard coded reports ... > Data sources - varied syntaxes and settings ... >

Return to chapter overview

Access97 database file ...

=====================================================================================================================================================

==================================================================================================================================================================================================

==============================================================================================================================================================================================================

==============================================================================================================================================================================================================

====================================================================================================================================================================

PHH - 24th. January 2018.


Third - coded in-memory object approach

==========================================================================

Not sure about SQL, I always use in memory objects, but I imagine the principle is similar

to the following.

Basically you create an instance of your XtraReport class, then simply assign your collection

to the DataSource property, so something like

XtraReport myreport = MyDXReport();

myreport.DataSource = GetMyCollection();

And that's it basically.... then you just assign myreport to the DocumentSource property of the

DocumentPreviewControl, and voila! So you can have a ViewModel where you create the report

instance, assign the collection to it's datasource, and have the report as a public property of

the VM. Then your View just has the DocumentPreviewControl and you bind the DocumentSource

to the property with the report.

For my collection datasources, I typically have two constructors - a parameterless one that returns

dummy example data, and which is used when designing a report in the report designer

(hence my comment the other day about the constructors with parameters maybe causing problems),

then the proper constructor which returns real data from the program, which is what is inserted into the

DataSource property at runtime.

==========================================================================

PHH - 24th. January 2018.


'Designer template' - add a Report item to an app ...

Well then guys, in this sub-section we have some Good news and some Bad news.

The good flavour of the news is that 'DevExpress' have provided a template for use while

creating the application, which can be used to graphically design and create a Report class

which is then added for us to the SE pane we are developing. This class of Report can

then be easily used in various parts of our application.

Unfortunately, at the current moment we do not have such a template available to us guys

in the X# integration within Visual Studio.

I am trying some different approaches with Class Libraries to see if I can get around the issue

for now, but I think I need to involve Robert and Fabrice on this matter.

All in good time, lets first see what is there and what we can get working.

Looking at what is available within C# :

Lets keep this first discussion to talking simply about a C# application with WPF forms, and

also class libraries for C# code classes as well.

In the following image we see a screen capture (shot) of the collapsed solution folder

structure - shown within the SE pane :-

======================================================================

================================================

===============================================

========================================

==========================================================================================================

====================================================================================================================================================

And 'XtraReport2' looks like the next image :-

=======================================================================================================================================================

Lets now see how to add a new Report class - of type 'XtraReport1'.

This can be done for either the Class Library, or the main application itself.

We will highlight the root of the library folder see below :-

=============================================================================

After highlighting 'ClassLibrary1', click the right mouse button to get the context menu,

and then select Add > New Item > ............

This brings up the 'Add New Item' dialog screen, seen below :-

=============================================================================================================================================

From the C# folder options in the LHS template tree, choose Reporting, and then the Wizard

for version 17.2 - I need to remove the earlier version entry but don't know how - will work on it.

I know, I will ask the Forum guys for their ideas.

Lets move on ........... here in the next image is the start of the Wizard series of dialog

input forms - check the details carefully :-

====================================================================

Yes, we will try the Access data file that DX provided with one of their samples.

I have placed it in one of my Temp folders at the root of my C: drive.

Now the next Wizard screen looks like this :-

=======================================================

I have selected all Tables on offer - as seen in the above image.

At the moment it is not clear to me what I should have selected above - BUT - a

good Wizard should let us continue without throwing nasty errors ;-0)

[ .. I need to experiment more with the Wizard for Access data files .. ]

Lets keep going and choosing what seems sensible ............. :-

======================================================================================

And then when we hit 'Finish' we see this below :-

===========================================================================================================================================

If I have gotten my imaging items right, and not confused some of the many images I have captured while doing

the Wizard process 'live', then the preview should look something like this below :-

===========================================================================================================

Okay, its not very well spaced out - and could do with a landscape mode being used as well - BUT - the

main point of the exercise was to create a coded Report class in out SE pane by the use of a GUI Wizard

feature / facility - which we have done.

================================================================================================================================

=================================================================================================================

PHH - 26th. January 2018.


Using DX Report Designer with XIDE ...

This is a special sub-section for my friend Wolfgang Reidman, I will need his help to write the details

within the parts of this section.

Wolfgang is a user of XIDE and also codes WPF stuff too.

I would like to involve him in an exciting experiment wit DX and the runtime 'Designer' of reports.

Watch this space ...................

===========================================================================

WPF Designer / Reporting :

WinForms Designer / Reporting :

PHH - 15th. January 2018.


Licenced software - using it without restriction ...

A few days ago, when I first came to DevExpress tools, I had only an expired 30 day trial version to try out.

Unfortunately, a refresh of the old 30 trial download did not 'stick', and so I had to start with limited access to the tools.

Once I realised how good they were to use, and how professional they felt, then I realised that I had to get some 100%

working tools so that I could fully explore DX for me then to write in these eNotes.

Here is what I now have from my Visual Studio IDE platform :-

==============================================================================================================================

You can see that I have had 'VERY special' permission to use the Reporting and WPF tools until after Cologne 2018 (the German

conference on X# Live etc.).

Once the Conference even is over I will invest in my own copy of these, simply because they are so good!

Now that I have a registered and licensed version of DX I see more functionality available to me - BUT - I still seem to

have an error relating to me printing out to paper - I am investigating, so watch this space .............

Now then folks, lets see what a clean start to a new solution / project looks like with the registered software.

I started by closing down the solution that I had open in VS 2015.

Then I opened / started a new Solution with an X# WPF application template - this this below :-

================================================================================================================================

Once the app was started the designer screen opened and showed the toolbox to be a seen below :-

[ .. note the DX version number is 17.2 .. ]

====================================================================

The WPF template provided most of the script shown below, all except line 11 which

was added when I dragged onto the design surface from the toolbox a 'ReportDesigner'.

===================================================================

The simple act of adding the ReportDesigner to the XAML script did a lot of work behind the scene

it would seem.

Take a look at the references section below - checkout all the v17.2 entries, there appears to be 31 :-

====================================================

When we collapse the References folder we see quite a small entry in the SE pane :-

====================================================

And don't forget we have not added any X# code as yet, either manually or by default - just the simple

template outline we usually get for a WPF application.

The usual .PRG file is seem above in the SE pane.

Now then, what do we see under the drop-down menu entry ? Study the following details :-

==============================================================================================================================

And what of the project 'converter' ? Check this below :-

==========================================================================================

As you can see above there is a clear reference to two different version of the DX tools, that

for 15.2 and the other for 17.2

In the newest (17.2) you can see below that the Wizard menu bar icon is missing - it is no longer there.

See below :-

==========================================

However, in version 15.2 (now called the older version) the Wizard icon was as in the following image :-

=======================================================

It would seem to be a good guess that 17 refers to a version of 'DevExpress' which was released in 2017.

I will leave it to you to work out what 15.2 refers to ;-0)

Issue to be sorted out - paper printout :

Even with the current and appropriate licences in place, and up to date, I still see an error - check below :-

==========================================================================================================================================

I will need to consult others on this issue - doesn't feel right to me.

Lets move on to another sub-section ...................

To learn some more interesting 'stuff', and new knowledge perhaps.

PHH - 5th. January 2018.


using the Wizard for report creation - in XSharp ...

Okay then guys, we all know what a Wizard is, it is really a process of us being led by the hand

when a complex set of inputs and selections need making, and usually in an order, one before another.

Keeping to the Wizard choices and options will usually (always?) lead us to a working result.

ALSO, with all Wizards, we can usually by-pass the Wizard itself and make the choices and selections

ourselves from separate and individual menu options.

The above usually takes more knowledge and experience on our behalf as developers and users of the tools.

Lets do some experimenting ................

Does the below icon still exist in the 17.2 version ?

=========================================================

Okay, as I suspected there has been a change while going from version 15.2 to 17.2 of DX tools.

The top ribbon bar of the Report Designer now looks like this shown below :-

==========================================

The Wizard is now presented to the user / developer as soon as a new report is requested by them clicking

the ' + ' sign at the top of screen, just after the last Report tab - check this out below :-

========================================================================================================================================

So in other words we are now forced to follow through the Wizard dialog screens, whether we like it or not.

I would guess however that once we have the correct 'shape' of a new report that we can change a

few things by visiting the Properties pane. I will give this a try ............

Before I go and experiment, lets see what my latest report looks like - and yes, I know, it is rather a meaningless report, simply because I put

no real thought into its design, other than wanting to use two SQL tables from the same trial database.

Study the following, more to see its form and function (shape and style) , rather than the data details :-

[ .. now that I am using a registered and activated, and current version, I have been able to change text colours and background .. ]

======================================================================================================================================================

If you are out of your 30 days trial period the ability to change text properties changes, it is greatly reduced.

As to the ability to print to paper - or will it ?!

============================================================================================================================================

Oh! dear me, I will need to get back to the DX guys as this should not happen I feel, with a registered copy of their tools.

I will investigate fully - and ask Nick too ;-0)

Lets move on, another sub-section to visit and study ..............

PHH - 31st. December 2017.


a Full basic Report app - show as well as create ...

The planning behind this straight forward app is to allow users to select any previously

saved Report 'Preview' - from drop down (combo) boxes on simple WPF forms.

Another option from the main WPF form allows the user to call up the Designer tool and

create new and further reports - which will show in the existing drop down lists.

We will operate around a folder-path which will the the folder 'home' of saved 'print' reports.

PHH - 21st. January 2018.


Navigation: 'Printing and Printers', and Report generation too ... > Third Party report generators for .NET > DevExpress Reporting > a Full basic Report app - show as well as create ... >

Return to chapter overview

working script and code

PHH - 21st. January 2018.


Navigation: 'Printing and Printers', and Report generation too ... > Third Party report generators for .NET > DevExpress Reporting > a Full basic Report app - show as well as create ... > working script and code >

Return to chapter overview

Preserving our 'Style' settings ...

==================================================================================================

===========================================================================================================================

========================================================================================================================================================

============================================================================================================

=============================================================================================================================================

===================================================================================================================

=====================================================================================================================

================================================================================================================================

=========================================================================================================================

PHH - 8th. February 2018.


Printing Labels - 15.2 version

==================================================================================================================

====================================================================

=====================================================================================================================

========================================================================================================================

===================================================================

Marquee selection of Fields :-

===================================================================================

Now easy the field content with some padding greater than zero :-

===================================================================

PHH - 31st. December 2017.


Navigation: 'Printing and Printers', and Report generation too ... > Third Party report generators for .NET > DevExpress Reporting > Printing Labels - 15.2 version >

Return to chapter overview

newer 17.2 version, same project

PHH - 5th. January 2018.


Barcodes - general use and display ...

===================================================================================================================================================

=========================================

==============================================================================================================================================

========================================================================================================

PHH - 12th. February 2018.


QR codes - the modern 'rage' / fashion ...

Okay guys, lets get ourselves right slap bang up to date with one of the latest fashions.

QR codes were invented by some guys in Japan working for "Denso Wave", way back in 1994.

Yes, QR (Quick Response) codes were developed 24 years ago.

Now that mobile / smart phones are just about everywhere, there is a great 'explosion' of the use

of QR codes, for just about everything - even the new Windows greenish 'screen of death' has one.

QR code can be read by the camera in smart-phones, as long as you have a suitable app downloaded

and installed.

Here is one I just seem to have hanging around :-

After a bit of fun and some fiddling around we can see this :-

[ .. System_Thread_Exception_Not_Found .. ]

The above information is specific to me and my PC crash issue.

Whereas the information encoded in the QR code says this ......

https://www.windows.com/stopcode

Which is static information.

Not sure if anyone anywhere has found it solves their immediate problem however ;-0)

And don't forget that the browser search / link, takes place on your phone, NOT the computer

which has just died on you !

Sorry about the dusty screen in the physical photo image I snapped with my camera, and the

flash did not help either I suspect - still, you get the idea.

Obviously if Microsoft provide this QR on each PC when it dies, they don't not expect the man

in the street to have any sort of specialised bar-code reader.

Except we could say the mobile phone / smart-phone is such a device, just that its so common

and has a wide range of uses.

Apparently we live in a QR Revolution.

Here is a clearer image and one which I did NOT create ;-0)

This is the blue screen of death, that from Insider versions of Windows 10 is a yucky green sort of colour.

There are quite a handful (or two) of little books on the subject of QR codes - see the Introduction section

of these eNotes and look at the "Reference books and other material" ..... section.

Before we leave this sub-section, let me say this - some basic facts to know about QR codes :-

Basic concepts, ideas, and behaviour :

QR codes can hold a lot of data compared to a regular one - Judith in her books say 200 times and

this also re[resents a 30 fold increase in data density.

High speed and multi-dimensional reading.

Resistant to damage and dirt.

Resistance to 'warp' - on surfaces which are far from flat.

*Support / enables other languages with character sets like Chinese, Japanese, and others.

High capacity printing in less space.

***CARE*** with QR reader, I have a good one on my Windows phone - BUT - it can't display the

"O umlaut" ( ö ) when I read the QR code for the German Conference. I get a black question

mark on a white diamond shaped background.

Here is what I tried to read :-

==============================================================================================

Karl suggested a different reader - I will investigate.

I have just done this and now have an app which was free and also does ordinary bar-codes as well

as QR codes with the ASCII extended character set.

Thanks Karl for the tip !

Here below is another free download which seems to scan QR codes with extended ASCII,

as well as reading other bar-codes, such as ISBN numbers.

Here is the result of reading my Cologne QR above - read directly from the monitor :-

============================================================

And here is another photograph of the main menu of this app

=============================================================

*** Problem ***

There is a problem often associated with FREE 'stuff'. This app seems to want me to

sign in to Facebook at every turn which ways - its most annoying but it seems to work.

And it did read the standard format bar-code of the ISBN on the back of Judith Sansweet's

little book.

Time to move on I feel ..............

PHH - 14th. February 2018.


(WinForms) DX Sample (C#) - QR from text provided ...

You can make your own QR reader and displayer from the following simple DX sample.

Try this out first - the instructions have images to match them shown sequential below :-

1.Create a new Windows Forms Application project.

2.Add reference to the DevExpress.Docs.v17.2.dll and the DevExpress.Data.v17.2.dll assemblies.

3.Drag a Button from the Toolbox and drop it on the form. Drag a PictureBox from the Toolbox and drop it on the form.

4.Insert the code listed below into the method that handles the Click button event.

Here is what my SE pane content looks like after following

the above instructions :-

=========================================================

Here is the C# code added to the button click event handler method :-

==============================================================================================================

And now the two extra references added from the Assemblies / Extensions list :-

===================================================

Now when we compile and run the C# applet we see this, but only after clicking the

long button :-

===========================================================

Lets make the QR code a bit bigger - see lines 30 through 33 below :-

=================================================================================================================================================================

Well, that worked great, even though I have not done WinForms for MANY years ;-0)

I will now try and convert this to a WPF and X# data bound applet.

Fingers crossed ..................

PHH - 12th. February 2018.

Addendum :

Since it was Meinhard who asked me to cover printing of QR's then here is a step forward I fee :-

[ .. note that line 22 is commented out .. ]

============================================================================================================================

Okay folks, lets now move on with our new found knowledge.

Let us start to design a QR suitable for the German conference in mid April of 2018.

Here is a start, and I have even figured out how to get the 'O' umlaut character on my mini-keyboard.

[ .. blue function key and the the blue 'Num Lock', followed by holding down 'Atlt' then pressing 1,4 and 8 .. ]

=====================================================================================================

I have even downloaded the QR free app to my Windows phone and can now read and check the QRs which

appear on my screen.

Who said 'I' was a dinosaur ?

PHH - 13th. February 2018.


(WPF) DX Sample (X#) - QR from supplied text ...

When converting the DX sample app provided, from its C# syntax and using WinForm technology,

we have a couple of issues to handle correctly.

The conversion to an X# WinForms app was easy but there were issues to tackle when we wished to

have a WPF and binding applet created and working.

Nothing earth shattering, but they did make me stop in my tracks just a little.

We need to deal with how we bind the image object to the 'Source' property of the 'Image' control on

the WPF form.

In binding the image object we have from the BarCode object we will find that we need to convert the

image type we have from the BarCode into something suitable for the Image control to accept.

I always find matching the image types in WPF stuff just a bit tricky. I can't really remember how easy

or difficult it was in WinForms, as its so long ago that I really used them in my own coding.

With these two things in mind ............

Lets get our sleeves rolled up and try to provide a bit of a walk-through section below :-

===========================================

Above is the image of the sample project in my SE pane.

And VERY importantly we need to study the required assemblies for the References section :-

===================================================

DevExpress assemblies seem obvious - BUT - make sure one or two of the others don't catch you out.

Okay then guys, moving on to the graphical design surface we have a simple enough setup - check this out :-

===========================================================================================================================

Just a button and an Image control.

Make sure you create a event for 'clicking' of the button, and complete the X# code in the PRG file

where the even handler method is to be found.

Study the XAML script details required - in the following image :-

=====================================================================================================================

Notice above line 11, where we set the Binding. This is coupled with the code line below at position /

line number 51.

In the image after the following we see on line 51 the 'DataContext' property being set.

And now it is time view the collapsed version of the main window class for WPF :-

==================================================================================================================================

As you can see above, I have added two methods to the form's main class definition.

In a bigger app you may place this somewhere more suitable / meaningful.

One methods is an 'event handler' and the other a STATIC method to convert the image type ready for 'Binding'.

Lets checkout the 'event handler' first, this is basically an X# version of what was in the C# WinForms sample app :-

====================================================================================================================

[ .. line 49 can be ignored and even removed in your test code .. ]

In the above image on line 51 we see the use of the STATIC 'translation' method. (It turns one image format

into another.)

This STATIC method converts the image type we have available on the BarCode object, to that suitable for

direct use on the Image control - the 'DataContext' property shows that we are binding the image data

to the relevant 'Source' property, as set in the XAML script.

The code within the method is all a bit unknown to me, I have just copied it - after a Google !

==========================================================================================

I got the code and general approach from 'Stackoverflow', then translated it to X#.

Bitmap related 'stuff' is all a bit of a mystery to me, I have to confess ;-0)

https://stackoverflow.com/questions/6484357/converting-bitmapimage-to-bitmap-and-vice-versa

Okay then, lets compile and run the exact same code and script as seem above - here is the result :-

[ .. after clicking the button that is .. ]

Obviously, we have to click the long Button at the top of the form to get the QR code to show.

Moving forward - it should be now obvious as to how we can create an interactive applet based on this test one.

We could provide a combo box or list box, with text optional inputs. Then at selection time have the

new QR code drawn on the form.

Further to that we could even input text 'live' and have the QR code displayed - then even store it perhaps !?

Lots of options ................

See you in the next sub-section.

PHH - 13th. February 2018.


Navigation: 'Printing and Printers', and Report generation too ... > Third Party report generators for .NET > DevExpress Reporting > Barcodes - general use and display ... > (WPF) DX Sample (X#) - QR from supplied text ... >

Return to chapter overview

working script and code

===============================================================================

<Window x:Class="WpfXS_QRcode_01.WPFWindow1"

      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

      Title="WPF in XSharp : QR codes :" Height="350" Width="660">

  <Grid>

      <Button x:Name="button" Content="QR show ! - Cologne 2018" HorizontalAlignment="Left"

              Margin="10,10,0,0" VerticalAlignment="Top" Width="192" Click="button_Click"/>

      <Image x:Name="image" Height="219" Margin="10,78,10,0"

            VerticalAlignment="Top" Source="{Binding Mode=OneWay}" Stretch="Fill"/>

  </Grid>

</Window>

===============================================================================

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.Navigation

using System.Windows.Shapes

//------------------------------------

using DevExpress.BarCodes

using System.Drawing

using System.Drawing.Imaging

using System.IO //--- MemoryStream ---

//------------------------------------

BEGIN NAMESPACE WpfXS_QRcode_01

  PUBLIC PARTIAL CLASS WPFWindow1 ;

      INHERIT System.Windows.Window

      PUBLIC CONSTRUCTOR()

           InitializeComponent()

      return

      private method button_Click(sender as object, e as System.Windows.RoutedEventArgs) as void

          local barCode as BarCode

           barCode := BarCode{}

           barCode:Symbology := DevExpress.BarCodes.Symbology.QRCode

          //barCode:CodeText := "http://www.devexpress.com"

           barCode:CodeText := "Meinhard, is this what your required ?"

           barCode:BackColor := System.Drawing.Color.White

           barCode:ForeColor := System.Drawing.Color.Blue

           barCode:RotationAngle := 0

           barCode:CodeBinaryData := Encoding.Default.GetBytes(barCode:CodeText)

           barCode:Options.QRCode.CompactionMode := QRCodeCompactionMode.Byte

           barCode:Options.QRCode.ErrorLevel := QRCodeErrorLevel.Q

          //barCode:Options.QRCode.ShowCodeText := false

           barCode:Options.QRCode.ShowCodeText := true

           barCode:DpiX := 144

           barCode:DpiY := 144

          //barCode:ShowPrintPreview()

           MessageBox.Show(barCode:BarCodeImage.ToString(),"what is this ?")

          self:image:DataContext := ToBitmapImage(barCode:BarCodeImage)

      return

      public static method ToBitmapImage(bitmap as Bitmap) as BitmapImage

          var memory := MemoryStream{}

           bitmap:Save(memory, ImageFormat.Bmp)

           memory:Position := 0

          var bitmapImage := BitmapImage{}

           bitmapImage:BeginInit()

           bitmapImage:StreamSource := memory

           bitmapImage:CacheOption := BitmapCacheOption.OnLoad

           bitmapImage:EndInit()

           bitmapImage:Freeze()

      return bitmapImage

  END CLASS

END NAMESPACE

===============================================================================

===============================================================================

PHH - 13th. February 2018.


Interactive app to generate QR code on screen ...

Okay guys, before we zip off and learn something new, lets see what the interactive app looks

like when running - check this out below :-

Above we have a WPF form which allows the user to enter into the top left hand purple (Thistle)

box the text required on the QR code.

We should be able to enter any text into the box.

The input box is a TextBox control and it is bound to a public property defined in the X# code.

There is a Button to cause the creation process to start - see the Tooltip below :-

=====================================================

When the 'action' Button is pressed the text in the input TextBox is made into a QR code using

the tools within the DevExpress assemblies being referenced.

Lets checkout the References required, and the SE pane generally :-

======================================

Above we see quite a minimal project structure.

This has a more extensive References section however - check below :-

==============================================

I had to add the top three entries but notice also that 'System.Drawing' is also required in

the references list.

Now lets checkout the Namespaces at the head of our coding :-

=======================================================================================

I leave all the Namespaces inserted by the X# template used to create the WPF form, and

then add the extra ones required as I develop the code.

So the later namespace code lines 'using' entries tend to be those for the technology I am testing.

It is probably a good time to look at the X# code in a collapsed form, just so we get a feel for what

is the overall structure of the main form PRG - see below for yourself :-

=============================================================================================================================

The code line 29 through to 59 are mostly about providing a public property which can then be

bound to the suitable properties on the XAML / WPF controls.

Since the code to make (create) the QR code is the same as the previous simple sample, we

may be better advised to see in depth how I have bound the data to the WPF controls. As this

is often a tricky thing to get right.

So just a swift look at the click event method handler :-

[ .. line 67 shows where we use the public data property (used for binding) - its a string .. ]

====================================================================================================================

So there was little difference in the above code from that we used previously.

Now lets look at the public property and the rest of the code / stuff to support data binding.

A private field backs the public property.

And a BIG 'must have' is the event for 'PropertyChanged', which tracks any input changes. Without the

code structure shown for tracking changes in the property 'QRtext' the action of the Form will not be

as we require - it just won't work!

Study the following carefully then look at how it corresponds to the script in the XAML file :-

==================================================================================================================

Okay then, the above image shows code which is very common in the eNotes I have been making

over the last four or more years. Data Binding needs public properties and also properties which respond

correctly to changes in the property value.

So how does this reflect in the script we need to provide in the XAML file ?

Lines 5, 17, and 22 show data binding script which is necessary to support the input / editing of the code text.

Note line 13 is simply the modern way of placing the image file details on the surface of the control. And as

such is not relly what we would class as 'Data Binding'.

Please also note below that most of the property details are standard and could be removed to the Resources

section of the Grid if we made one - I usually do this but here we do ONLY have five controls.

Study the details carefully :-

======================================================================================================================================================

Okay then, you will have to take it from me that the app compiles and runs quite nicely, as designed, and expected.

However, there is one thing we MUST checkout, and that is what happens when we click the Button and

the input text box content is an empty string ?

Lets try this - see below :-

==============================================================================================================

Well, no crashes and/ or error messages ;-0)

So now lets try a nice positive message - one to warm our hearts.

Note that this screen clip shows the app in debug mode, and please realise that the TextBlock

control simply serves as an easy way to see that the data binding is active and working correctly.

Anything that appears in the TextBlock will also be used directly for the QR code generation.

==============================================================================================================

Okay the folks, so far so good.

Lets now try something a lot more ambitious. We can try a much longer link, to see

if in fact the Form re-sizing allows us to display a square shaped QR code.

All we need do is grab and hold the bottom RHS corner of the form and move the mouse cursor to a suitable place.

Here is what mine now looks like :-

Yep! while this image was on my monitor and during my typing of these words, I used my Windows phone to

read the QR code and the text contents came through 100% correct. Correct meaning the same characters etc..

This is what we don't want displayed before reading :-

And here is the 'Tooltip' showing what we should always do with our QR code :-

Okay then, success it would seem - BUT - I have had another and interesting idea, what if we change

app Form to allow the user to choose a colour for the QR code when created?

I need to go and think of some suitable data binding code. I did something like this years

ago after help / suggestions from Terry Bourne - we can have a drop box with Color names and the

Background property bound to the displayed text.

Keep your fingers crossed ............

PHH - 15th. & 16th. February 2018.


Navigation: 'Printing and Printers', and Report generation too ... > Third Party report generators for .NET > DevExpress Reporting > Barcodes - general use and display ... > Interactive app to generate QR code on screen ... >

Return to chapter overview

working code and script

===============================================================================================

<Window x:Class="WpfCreateQRcodeXS.WPFWindow1"

      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

      Title="WPF : X# : QR code creation :" Height="420" Width="660"

      Name="QRwindow" Background="Bisque">

  <Grid>

      <Button x:Name="button" Content="QR create !! - Cologne 2018" HorizontalAlignment="Left"

              Margin="315,55,0,0" VerticalAlignment="Top" Width="192" Height="25"

              Background="AliceBlue" Click="button_Click" FontStyle="Italic" FontWeight="Bold"/>

      <Image x:Name="image" Margin="10,126,10,10" Source="{Binding Mode=OneWay}" Stretch="Fill" />

      <TextBox x:Name="textBox" HorizontalAlignment="Left" Height="56" Margin="10,55,0,0"

              Text="{Binding Path=QRtext, ElementName=QRwindow, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"

              VerticalAlignment="Top" Width="300" TextWrapping="Wrap"

              Background="Thistle" Foreground="Black"/>

      <TextBlock x:Name="textBlock" Margin="318,85,0,0" Height="24"

                Text="{Binding Path=QRtext, ElementName=QRwindow, UpdateSourceTrigger=PropertyChanged}"

                VerticalAlignment="Top" TextWrapping="NoWrap"

                Background="Honeydew" Foreground="Red" HorizontalAlignment="Left" Width="324"/>

      <Label x:Name="label" Content="QR code : Change / Edit / Input :" HorizontalAlignment="Left"

            Margin="10,24,0,0" VerticalAlignment="Top" Width="300"

            Background="LightBlue" FontWeight="Bold" FontSize="16"/>

  </Grid>

</Window>

===============================================================================================

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.Navigation

using System.Windows.Shapes

//--------------------------------

using DevExpress.BarCodes

using System.Drawing

using System.Drawing.Imaging

using System.IO //--- MemoryStream ---

USING System.Collections.Generic

USING System.Text

using System.Collections.ObjectModel   //----- ObservableCollection .....

using System.ComponentModel   //----- INotifyPropertyChanged .....

BEGIN NAMESPACE WpfCreateQRcodeXS

  PUBLIC PARTIAL CLASS WPFWindow1 ;

      inherit System.Windows.Window implements INotifyPropertyChanged

      //----- fields -----

      private _QRtext as string

      public constructor()

           InitializeComponent()

          self:QRtext := "<... Enter code text ...>"

      return

      //----- properties -----

      public property QRtext as string

          get

              return self:_QRtext

          end get

          set

              self:_QRtext := value

              self:NotifyPropertyChanged("QRtext")

          end set

      END PROPERTY

      //----- events -----

      event PropertyChanged as PropertyChangedEventHandler

      //----- methods -----

      method NotifyPropertyChanged(propertyName as string) as void

          if (self:PropertyChanged != null)

              self:PropertyChanged:Invoke(self, PropertyChangedEventArgs{propertyName})

          endif

      RETURN

      private method button_Click(sender as object, e as System.Windows.RoutedEventArgs) as void

          local barCode as BarCode

           barCode := BarCode{}

           barCode:CodeText := ""

           barCode:Symbology := DevExpress.BarCodes.Symbology.QRCode

          var cText := self:textBox:Text:Trim()

          //MessageBox.Show(cText,"my Input to QR :")

          //barCode:CodeText := cText

           MessageBox.Show(self:QRtext,"my Input to QR :")

           barCode:CodeText := self:QRtext

           barCode:BackColor := System.Drawing.Color.White

           barCode:ForeColor := System.Drawing.Color.Blue

           barCode:RotationAngle := 0

           barCode:CodeBinaryData := Encoding.Default.GetBytes(barCode:CodeText)

           barCode:Options.QRCode.CompactionMode := QRCodeCompactionMode.Byte

           barCode:Options.QRCode.ErrorLevel := QRCodeErrorLevel.Q

           barCode:Options.QRCode.ShowCodeText := true

           barCode:DpiX := 144

           barCode:DpiY := 144

          self:image:DataContext := ToBitmapImage(barCode:BarCodeImage)

      return

      public static method ToBitmapImage(bitmap as Bitmap) as BitmapImage

          var memory := MemoryStream{}

           bitmap:Save(memory, ImageFormat.Bmp)

           memory:Position := 0

          var bitmapImage := BitmapImage{}

           bitmapImage:BeginInit()

           bitmapImage:StreamSource := memory

           bitmapImage:CacheOption := BitmapCacheOption.OnLoad

           bitmapImage:EndInit()

           bitmapImage:Freeze()

      return bitmapImage

  END CLASS

END NAMESPACE

===============================================================================================

<Application x:Class="WpfCreateQRcodeXS.App"

  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

  StartupUri="WPFWindow1.xaml">

  <Application.Resources>

  </Application.Resources>

</Application>

===============================================================================================

==================================================

===============================================================================================

PHH - 16th. February 2018.


Improved interactive app - 'QR code', choosing a colour ...

Okay then guys, lets have some fun and also improve our coding and design skills.

Here is what we have so far with our interactive QR code generating app :-

==================================================================================================

Nice enough you may think, however, the blue colouration is present due to a short line of hand entered

X# code.

What would be even nicer is if we could provide a drop down box with Color 'swatches' to choose from.

A 'swatch' is a small sample (or piece) of coloured cloth or material.

The idea is that after selecting a colour, the "QR create" button is clicked and the new QR code made

in the colour of the chosen 'swatch'.

We will have to solve some interesting little problems and issues on the way to a successful working

solution / example.

If and when we get this idea to work, we may even add a second drop box selection for the background

colour of the QR code - but one thing at a time.

Lets get going ..............

Here is a visual idea of what we are after - check this out :-

=========================================================

Yes, we are going to have a combo box control showing the available colours from which to choose.

We will need to do something about the Names not showing very clearly on some of these swatches.

Currently, as shown above, we just have a handful of hand/hard coded colours in the drop list.

Here is what I was using to make the above runtime image :-

=============================================================

Note that in the early test code line 54 above is where we complete the binding of the collection to the GUI control.

In the following code line 46 is where we set up the possibility of the 'ItemsSource' binding - at the RHS of the line:-

=======================================================================================================================

While we are studying this XAML script snippet, lets also examine lines 47/48 in which we bind the Selected Item

in the 'combobox' drop list to a public property made within the main PRG file of the Form class.

Lets move on a pace or two ............

How to find all the Colors available to us :

This is an interesting process .........

The plan is to find by Reflection, all the possible 'color's - that is, the properties of the 'Color' structure

within the 'System.Drawing' namespace.

=============================================================

And here is a bit more information, before we roll up our sleeves and get some results :-

===============================================================================================================

Note the 'A', as this is one of four similar properties which return a Byte value - R, G, and B are the others.

[ .. these stand for Red, Green and Blue .. ]

Now then, lets view a slice of success before we study any more X# code and syntax :-

[ .. see how the properties appear after being found .. ]

======================================================================================

In the above image we see a few lines of code using Reflection to find all the Properties of the Color structure.

Yes, its a 'Structure' and not a Class - although you can think of a Structure as a 'light-weight' Class.

Above, the first line after 'try' is what retrieves the properties - it is also seen below at line 57 :-

By observation I noticed that all the colour (named) related properties contained the string of characters

which line 62 is looking for.

This was the simplest way to get to all the Colors as we know them.

Checkout the following details :-

[ .. there are 150 of these it would appear / seem .. ]

=================================================================================================================

So all we need to do is strip out the extra characters to leave each drop list entry a simple named colour.

Here is the finished code as well as the final drop list collection :-

==========================================================================================================================

Okay guys, so far so good - these look great!

So what do we need to do now to improve our interactive app ?

Well, lets see if we can improve the way the names of the individual colours are displayed on each 'swatch'.

Look at the image below to see that changing to only one colour always leaves some names unreadable :-

===============================================================================================================================================

Note that the XAML script is not complete as yet - only the background color display is sorted out.

Still, with what we have we can code (connect up) the selection from the drop list to the QR code displayed.

Check the following :-

===============================================================================================================

So instead of a one-off specific colour object being assigned to the Bar-code property, we can use

the object bound to the combo-box selection - check this out :-

====================================================================================

So we can now interactively choose and select a color from the drop list and we will

see this at QR code 'create' time.

How then folks, we may have the app working quite well - BUT - there is still an issue with how

we display the text name of the Color 'swatch' for each color entry.

Basically, we need a color for the Foreground (the text color) which will be reasonably easily

visible, and hence read okay.

For dark colors we need a light text, and for light colours a dark text.

Lets look at, and study, the following image - as of yet in our XAML script it is not right :-

====================================

We do NOT see text names standing out and 'readable' as the following image shows that we have the same color set as

both Fore and Back - ground colours :-

================================================================================================================================================

What we really need is a 'Converter' which can be applied to the Value binding in line 11, which examines

the Back color and depending on how dark it is, provide a returned value of light or dark.

We will study how we need to code a suitable 'Converter' class later in this section.

For now lets see how the actually selected color is applied to the correct property in the QR code

generating method.

We need to code a Public Property to hold the selected color item from the ComboBox. We need to

use data binding and so wee need to set up a Public Property in the standard way for data binding.

Check out the following code, to see the property changing related code - at the moment the Public

Property "SelectedColor" is collapsed :-

==================================================================================================================

And the Public Property is pretty standard really, so we will only visit it later after we have made changes to allows us to

have two drop boxes, and two PPs for selected colors. :-

For now we need to see the part of the Button click event handler method which uses the selected color object :-

[ .. checkout line 111 in particular ..]

========================================================================================================================

Notice in the above image line 107 is where we will make further changes later, so that we can apply a second

color selection.

Lets move on, we need now to take a good look at the XAML script, and see what is present to handle the drop lists

and the related data binding.

I have collapsed the Resources section as we will study that in detail later.

[ .. note line 5 where we name the window object .. ]

Lines 39 - 42 are the main lines to study - the rest is to show you the overall shape of the XAML script :-

=====================================================================================================================================

Line 41 and 42 are crucial to the binding of the selected item of the Combobox, and the end of line 40 is

where we bind the color Collection.

The 'Path' attribute / property points the script at the coded Public Property called "SelectedColor".

The other settings (property values) on lines 41/42 are to make the 'thing' work. They are required.

Okay then, lets see how this looks when we do a test trial - display seen at compile and runtime :-

Okay, great, moving on .............

The need for a 'Converter' :

The plan is to write a 'Converter' class which takes the background color and from this creates

a suitable foreground color - for each of the combo box items.

A 'Converter' is a system specified class, and we need to provide two virtual methods to get things

to work as they should. We need a 'Convert' and a 'ConvertBack'.

Please note that quite often we do not require the conversion in the 'reverse' direction, and so it

is often left as 'blank' or specified as nothing.

We are VERY interested in a 'Convert' and NOT a 'ConvertBack'.

The idea of a fore color being 'suitable' is so that the text can be read easily, and along the lines of

a light colour text being on a dark background, and vice versa.

I have an idea to be able to calculate from the RGB (red, green, blue) values whether the back color

is dark, light, or in between some place - then build a fore color to suit.

We will see .................... some time later, and a bit of faffing about ..........

'Converter' to the rescue - showing text in a Foreground colour to always be visible :

Note in the following explanation when I use the term 'Converter' I really refer to 'Value Converter'.

Okay guys we have a nice working solution which also looks good in use. Years ago Alwyn Jennings-Bramly

showed me this way of looking at, and handling colors. Thanks Alwyn.

Here below is a screen shot image of some X# code in a 'Converter' which, depending on the back colour,

displays the text (color name) in either white or black.

Basically, the code in the Converter calculates values to see if the 'supplied' back colour is either dark or light.

If the back colour is light then the fore colour returned is set to Black, and vice versa. Hopefully then, each and

every color text name can be read easily enough - this seems to be the case.

*** Remember *** be careful with 'Color' as there are two different Namespaces with a 'Color' class it

would seem. 'Life is never simple'. I have done more detailed reporting of this in another part of these eNotes.

Lets take a look at the 'Converter' class which is working :-

===========================================================================================================================

Note in the above image that we are really ONLY interested in the 'Convert' method and the 'ConvertBack' method

part of the 'Converter' class is not being used.

So in that case lets just checkout above lines 169 through 188.

Each color property (like Color.Red) is made up of three numerical parts, representing Red, Green, and Blue - called 'RGB'.

Each of the three values can be between 0 and 255 inclusive.

The Colour white is shown when all RGB values are maxed out, so 765 represents white.

Black on the other had is all three zero values, a total of zero.

The logic in lines 173 through 183 is simply if the back colour is darker (more like zero) then make the

fore colour light - in fact white. If the back colour is light / bright, then make the fore colour dark, in

fact in my code 'Black'.

The choice of the test value on line 179 is arbitrary - or subjective. "Suck it and see" is a British phrase

which would seem appropriate.

AND - if you look at and inspect the above coloured image you will see that we done seem to be able

to see and read the 'color' property names on the horizontal coloured swatches.

Lets now take a look at the XAML script to see where and how the 'Converter' is used / applied.

Study this carefully :-

=================================================================================================================================================

Ignore the wavy lines suggesting an error in the script - they seem to be incorrect and I have reported them to

Robert and Fabrice from the Team for X#.

Lines 17 and 18 are where the Converter is used.

And lines 5 and 9 are where we provide the 'link' details so that the XAML file can find the .NET code for the Converter.

The reading of lines 17/18 make interesting listening.

Basically, we are binding the Background property on the same (SELF) source (or element) but only

after it has been through a Converter object to process the provided 'Back' color and create a return color.

To be honest, I am not totally sure that the XAML syntax is correct, or if it has some unnecessary parts

to it. However, it does work a-okay, so I will leave it as it is.

Summary - we are binding one property on the 'ComboBoxItem' object to another property on the same element, and

in the process we are comparing the value being passed so that we may supply a suitable one to be bound.

Just before we move on, let me say that the rest of the functionality of the QR code creation, and its display is

as it was previously.

Here is some runtime proof :-

Study the details in the following image :-

==========================================================================================

At the top 'Crimson' is poorly displayed - BUT - the fore color was chosen to go over the top of

Crimson and not the altered colour due to the WPF drop box functionality when the item is selected.

Time to move on - lets go and learn some more fine 'stuff'.

PHH - 18th. February 2018.


Navigation: 'Printing and Printers', and Report generation too ... > Third Party report generators for .NET > DevExpress Reporting > Barcodes - general use and display ... > Improved interactive app - 'QR code', choosing a colour ... >

Return to chapter overview

working code and script

===========================================================================================

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.Navigation

using System.Windows.Shapes

//--------------------------------

using DevExpress.BarCodes

using System.Drawing

using System.Drawing.Imaging

using System.IO //--- MemoryStream ---

USING System.Collections

USING System.Text

using System.Collections.ObjectModel   //----- ObservableCollection .....

using System.ComponentModel   //----- INotifyPropertyChanged .....

using System.Globalization

BEGIN NAMESPACE WpfCreateQRcodeXS

  PUBLIC PARTIAL CLASS WPFWindow1 ;

                  inherit System.Windows.Window implements INotifyPropertyChanged

      //----- fields -----

      private _QRtext as string

      private _selectedColor as System.Drawing.Color

      public constructor()

           InitializeComponent()

          self:QRtext := "<... Enter code text ...>"

          self:comboBox:SelectedIndex := 0

           SetupColorCollection()

          //MyInitialise()

      return

      //----- properties -----

      public property SelectedColor as System.Drawing.Color

          get

              return self:_selectedColor

          end get

          set

              self:_selectedColor := value

              self:NotifyPropertyChanged("SelectedColor")

          end set

      end property

      public property QRtext as string

          get

              return self:_QRtext

          end get

          set

              self:_QRtext := value

              self:NotifyPropertyChanged("QRtext")

          end set

      end property

      //----- events -----

      event PropertyChanged as PropertyChangedEventHandler

      //----- methods -----

      method NotifyPropertyChanged(propertyName as string) as void

          if (self:PropertyChanged != null)

              self:PropertyChanged:Invoke(self, PropertyChangedEventArgs{propertyName})

          endif

      return

      public method MyInitialise() as void

          var value := System.Windows.Media.SolidColorBrush{System.Windows.Media.Colors.White}

          var myBrush := (SolidColorBrush)value

          var InputColorR := myBrush:Color:R

           MessageBox.Show(InputColorR.ToString(),"my red of white :")

          local InputColor := (System.Windows.Media.Color)(myBrush:Color) as System.Windows.Media.Color

          var oTestA := (int)InputColor:A

          var oTestR := (int)InputColor:R

          var oTestG := (int)InputColor:G

          var oTestB := (int)InputColor:B

          var oTestSum := oTestR+oTestG+oTestB

           MessageBox.Show(oTestSum:ToString(),"What is the sum ? :")

      return

      private method SetupColorCollection() as void

          //--- Color is a structure and the 'red' is a property of it ---

          //--- so we need to retrieve the properties for the list required ---

          try

              var myProps := typeof(System.Drawing.Color).GetProperties()

              //MessageBox.Show(myProps.Count().ToString(),"Count colors : ")

              //--- there are about 150 of these ---

              local implied myColors := List<string>{}

              foreach implied color in myProps

                  if color:ToString():Contains("System.Drawing.Color")

                      var fullName := color:ToString():Trim()

                      var chopName := fullName:Substring(fullName:LastIndexOf(" "))

                       myColors:Add(chopName)

                  endif

              next

              self:comboBox:DataContext := myColors

          catch oEX as SystemException

               MessageBox.Show(oEX:Message,"my Error structure colors :")

          end try

      return

      private method button_Click(sender as object, e as System.Windows.RoutedEventArgs) as void

          //MessageBox.Show(self:SelectedColor:ToString(),"my previously selected color :")

          local barCode as BarCode

           barCode := BarCode{}

           barCode:Symbology := DevExpress.BarCodes.Symbology.QRCode

           barCode:CodeText := self:QRtext

           barCode:BackColor := System.Drawing.Color.White

          //--- set and then try to over-ride ! ---

          //barCode:ForeColor := System.Drawing.Color.Blue

           barCode:ForeColor := self:SelectedColor

           barCode:RotationAngle := 0

           barCode:CodeBinaryData := Encoding.Default.GetBytes(barCode:CodeText)

           barCode:Options.QRCode.CompactionMode := QRCodeCompactionMode.Byte

           barCode:Options.QRCode.ErrorLevel := QRCodeErrorLevel.Q

           barCode:Options.QRCode.ShowCodeText := true

           barCode:DpiX := 144

           barCode:DpiY := 144

          self:image:DataContext := ToBitmapImage(barCode:BarCodeImage)

      return

      public static method ToBitmapImage(bitmap as Bitmap) as BitmapImage

          var memory := MemoryStream{}

           bitmap:Save(memory, ImageFormat.Bmp)

           memory:Position := 0

          var bitmapImage := BitmapImage{}

           bitmapImage:BeginInit()

           bitmapImage:StreamSource := memory

           bitmapImage:CacheOption := BitmapCacheOption.OnLoad

           bitmapImage:EndInit()

           bitmapImage:Freeze()

      return bitmapImage

  end class

   [ValueConversion(typeof(SolidColorBrush), typeof(SolidColorBrush))];

  class ColorChange implements IValueConverter

      virtual method Convert(value as object, targetType as Type, ;

                   parameter as object, culture as CultureInfo) as object

          try

              var myBrush := (SolidColorBrush)value

              var InputColorA := (int)myBrush:Color:A

              var InputColorR := (int)myBrush:Color:R

              var InputColorG := (int)myBrush:Color:G

              var InputColorB := (int)myBrush:Color:B

              var InputColorSum := InputColorR+InputColorG+InputColorB

              if InputColorSum > 450

                  return SolidColorBrush{Colors.Black}

              else

                  return SolidColorBrush{Colors.White}

              endif

          catch oEX as SystemException

              return SolidColorBrush{Colors.White}

          end try

          //return DependencyProperty.UnsetValue

      return SolidColorBrush{Colors.Red}

      virtual method ConvertBack(value as object, targetType as Type, ;

                   parameter as object, culture as CultureInfo) as object

          //return DependencyProperty.UnsetValue

      return SolidColorBrush{Colors.LimeGreen}

  end class

END NAMESPACE

===========================================================================================

<Window x:Class="WpfCreateQRcodeXS.WPFWindow1"

      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

      xmlns:converter="clr-namespace:WpfCreateQRcodeXS"

      Title="WPF : X# : QR code creation :" Height="420" Width="660"

      Name="QRwindow" Background="Bisque">

  <Window.Resources>

      <converter:ColorChange x:Key="myColorChange" />

  </Window.Resources>

  <Grid>

      <Grid.Resources>

          <Style TargetType="ComboBoxItem">

              <Setter Property="Background" Value="{Binding RelativeSource={RelativeSource Self}, Path=Content}"/>

              <!--<Setter Property="Foreground" Value="Red" /> simple assignment -->

              <Setter Property="Foreground" Value="{Binding RelativeSource={RelativeSource Self}, Path=Background,

                      Converter={StaticResource myColorChange}}" />

              <!-- above works OK! -->

          </Style>

      </Grid.Resources>

      <Button x:Name="button" Content="QR create !! - Cologne 2018" HorizontalAlignment="Left"

              Margin="315,55,0,0" VerticalAlignment="Top" Width="192" Height="25"

              Background="AliceBlue" Click="button_Click" FontStyle="Italic" FontWeight="Bold"

              ToolTip="Click this button to CREATE code"/>

      <Image x:Name="image" Margin="10,126,10,10" Source="{Binding Mode=OneWay}" Stretch="Fill"

            ToolTip="Drag the Form to re-size the QR code."/>

      <TextBox x:Name="textBox" HorizontalAlignment="Left" Height="56" Margin="10,55,0,0"

              Text="{Binding Path=QRtext, ElementName=QRwindow, Mode=TwoWay,

                              UpdateSourceTrigger=PropertyChanged}"

              VerticalAlignment="Top" Width="300" TextWrapping="Wrap"

              Background="Thistle" Foreground="Black"/>

      <TextBlock x:Name="textBlock" Margin="318,85,10,0" Height="24"

                Text="{Binding Path=QRtext, ElementName=QRwindow, UpdateSourceTrigger=PropertyChanged}"

                VerticalAlignment="Top" TextWrapping="NoWrap" Background="Honeydew"

                Foreground="Red" ToolTip="Just a copy of the bound string data."/>

      <Label x:Name="label" Content="QR code : Change / Edit / Input :" HorizontalAlignment="Left"

            Margin="10,24,0,0" VerticalAlignment="Top" Width="300"

            Background="LightBlue" FontWeight="Bold" FontSize="16"/>

      <ComboBox x:Name="comboBox" HorizontalAlignment="Left" Margin="522,55,0,0"

                VerticalAlignment="Top" Width="120" ItemsSource="{Binding}"

                SelectedItem="{Binding Path=SelectedColor, ElementName=QRwindow, Mode=TwoWay,

                                      UpdateSourceTrigger=PropertyChanged}"/>

      <Label x:Name="label1" Content="Select a Colour :" HorizontalAlignment="Left"

            Margin="522,24,0,0" VerticalAlignment="Top" Width="120" FontWeight="Bold"

            Background="LightGreen"/>

  </Grid>

</Window>

===========================================================================================

==================================================

===========================================================================================

===========================================================================================

PHH - 19th. February 2018.


Further improved interactive 'QR code' app ...

Okay guys the good news is that we can improve and embellish our previous app for QR codes by simply

using a lot of what we have already written, created, done.

We just need to make a few code and script changes.

What do I mean by the above ? Well, the color Collection we made for the first drop list can simply be used

for the second one as well, with no changes.

This then means that the 'Converter' does not need to be changed.

We need to add a second Public Property for the second selected color item from the drop list - BUT - it is

probably best to rename the first one to make the code more readable and more consistent too.

Lets just see how the app looks when run and shows the main screen :-

========================================================================================================

Okay then, so lets see the Public Properties we need for data binding :-

========================================================================================

Lines 32 and 33 show that the naming convention for the two are consistent and that the first one, from the

previous example, has been renamed to "SelectedColorF".

The 'shape' of the Public Properties for data binding are always along the following lines :-

====================================================================================

Okay, what I mean is that any other PPs have the Type changed and the names changed of the field

used and the Property changed 'Name'.

All the PPs I define for use with data binding in WPF are similar to that above. They work and so I stick

with it.

Now then, lets look at what comes out of the various parts of the long drop list (both are similar).

Below notice that the snippets are taken from middle, bottom, and top of the list in the drop box :-

===========================================================================================================================================================================

Notice in the first image above (LHS) that we see the point where the text changes from white to black, as the

back colours get lighter in 'shade' or 'hue'.

This is done by the 'Converter' (value) which we apply in the XAML code.

Lets see just how we apply the 'Converter' to the Style of the 'ComboBoxItem' :-

[ .. lines 17 and 18 are those using the Converter ..]

===================================================================================================================================================

[ .. ignore the wavy line - there appears to be an integration issue - with X# into VS .. ]

We need to examine closely the above script details.

Line 15 above reads as ... to the 'Background' property apply the 'Content' value from the same object.

This works for us simply because the system actually makes a SolidColorBrush from the text name in the

Content of the ComboBoxItem. So we get a brush with color - all based on the text value.

Line 17 above reads ... to the 'Foreground' property apply the value/object return from the Converter when

the input to the Converter is the value of the 'Background' property.

Because both ComboBox controls have 'Items' then this script works as is for both drop boxes.

Lets now move on to see the binding in the 'ComboBox' definition in the body of the Grid.

Lines 56 & 57 are to set up the binding to the Public Property called 'SelectedColorB'. The ItemsSource

is as it was from previous app versions - just binds the Color Collection :-

=========================================================================================================================

Now then, we need to get an over-view of the general structure of our X# code - check this out below :-

==============================================================================================================================

In the above code structure there are parts we should examine in more detail.

The way we find the Colors and then create a suitable Collection for display in the ComboBoxes is

probably the place to start.

ost of this code is new or has been changed, so read it through carefully, and in full :-

==================================================================================================================

Yes, binding occurs at lines 137 and 138.

Lines 129 - 131 are LINQ statement syntax - in order to make the sorted order 'Dark to Light' in colour shades.

The loop from 112 to 126 finds the sum of the Red, Green, Blue numerical values and stores them ready for

use later from the Collection of 'ColorNameValue's which is processed by the LINQ statement.

Remember that White is 255,255,255 and Black is 0,0,0 - so Light 'max is 0' and Dark 'max' is 765.

Here is the small and simple class 'ColorNameValue' :-

===============================================================================

And that's that as far as the explanation goes.

Here is the main screen of the app ready for further testing and using :-

Whoops ! - guys - we did not cover the Converter code I think - so here it is now :-

==============================================================================================

The choice of the comparison value is arbitrary and is done by eye, or trial and error - 'looks see'!

Likewise, the choice of Black/White is by choice I suppose we could try Dark Blue and Yellow. This

is left to you.

For me the Convert does what I required at the outset, and that is to make all the Color names readable.

See you in the next sub-section .....

PHH - 20th. February 2018.


Navigation: 'Printing and Printers', and Report generation too ... > Third Party report generators for .NET > DevExpress Reporting > Barcodes - general use and display ... > Further improved interactive 'QR code' app ... >

Return to chapter overview

working code and script

===============================================================================

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.Navigation

using System.Windows.Shapes

//--------------------------------

using DevExpress.BarCodes

using System.Drawing

using System.Drawing.Imaging

using System.IO //--- MemoryStream ---

USING System.Collections

USING System.Text

using System.Collections.ObjectModel   //----- ObservableCollection .....

using System.ComponentModel   //----- INotifyPropertyChanged .....

using System.Globalization

BEGIN NAMESPACE WpfCreateQRcodeXS

  PUBLIC PARTIAL CLASS WPFWindow1 ;

                  inherit System.Windows.Window implements INotifyPropertyChanged

      //----- fields -----

      private _QRtext as string

      private _selectedColorF as System.Drawing.Color

      private _selectedColorB as System.Drawing.Color

      public constructor()

           InitializeComponent()

          self:QRtext := "<... Enter code text ...>"

          self:comboBoxF:SelectedIndex := 0

          self:comboBoxB:SelectedIndex := 0

           SetupColorCollection()

          //MyInitialise()

      return

      //----- properties -----

      public property SelectedColorF as System.Drawing.Color

          get

              return self:_selectedColorF

          end get

          set

              self:_selectedColorF := value

              self:NotifyPropertyChanged("SelectedColorF")

          end set

      end property

      public property SelectedColorB as System.Drawing.Color

          get

              return self:_selectedColorB

          end get

          set

              self:_selectedColorB := value

              self:NotifyPropertyChanged("SelectedColorB")

          end set

      end property

      public property QRtext as string

          get

              return self:_QRtext

          end get

          set

              self:_QRtext := value

              self:NotifyPropertyChanged("QRtext")

          end set

      end property

      //----- events -----

      event PropertyChanged as PropertyChangedEventHandler

      //----- methods -----

      method NotifyPropertyChanged(propertyName as string) as void

          if (self:PropertyChanged != null)

              self:PropertyChanged:Invoke(self, PropertyChangedEventArgs{propertyName})

          endif

      return

      public method MyInitialise() as void

          var value := System.Windows.Media.SolidColorBrush{System.Windows.Media.Colors.White}

          var myBrush := (SolidColorBrush)value

          var InputColorR := myBrush:Color:R

           MessageBox.Show(InputColorR.ToString(),"my red of white :")

          local InputColor := (System.Windows.Media.Color)(myBrush:Color) as System.Windows.Media.Color

          var oTestA := (int)InputColor:A

          var oTestR := (int)InputColor:R

          var oTestG := (int)InputColor:G

          var oTestB := (int)InputColor:B

          var oTestSum := oTestR+oTestG+oTestB

           MessageBox.Show(oTestSum:ToString(),"What is the sum ? :")

      return

      private method SetupColorCollection() as void

          //--- Color is a structure and the 'red' is a property of it ---

          //--- so we need to retrieve the properties for the list required ---

          try

              var myProps := typeof(System.Drawing.Color).GetProperties()

              //MessageBox.Show(myProps.Count().ToString(),"Count colors : ")

              //--- there are about 150 of these ---

              local implied myColors := List<string>{}

              local implied myCNVlist := List<ColorNameValue>{}

              foreach implied color in myProps

                  if color:ToString():Contains("System.Drawing.Color")

                      var fullName := color:ToString():Trim()

                      var chopName := (fullName:Substring(fullName:LastIndexOf(" "))):Trim()

                      var omyColor := System.Drawing.Color.FromName(chopName)

                      var InputColorR := (int)(omyColor.R)

                      var InputColorG := (int)(omyColor.G)

                      var InputColorB := (int)(omyColor.B)

                      var InputColorSum := InputColorR+InputColorG+InputColorB

                       myCNVlist:Add(ColorNameValue{chopName, InputColorSum})

                  endif

              next

              //--- sort by colors dark to light ---

              var myCNVlistOrd := from cnv in myCNVlist;

                                  orderby cnv:ColorValue;

                                  select cnv

              foreach implied nameValue in myCNVlistOrd

                   myColors:Add(nameValue:ColorName)

              next

              self:comboBoxF:DataContext := myColors

              self:comboBoxB:DataContext := myColors

          catch oEX as SystemException

               MessageBox.Show(oEX:Message,"my Error making colors Ordered list:")

          end try

      return

      private method button_Click(sender as object, e as System.Windows.RoutedEventArgs) as void

          //MessageBox.Show(self:SelectedColor:ToString(),"my previously selected color :")

          local barCode as BarCode

           barCode := BarCode{}

           barCode:Symbology := DevExpress.BarCodes.Symbology.QRCode

           barCode:CodeText := self:QRtext

          //barCode:BackColor := System.Drawing.Color.White

           barCode:BackColor := self:SelectedColorB

          //--- set and then try to over-ride ! ---

          //barCode:ForeColor := System.Drawing.Color.Blue

           barCode:ForeColor := self:SelectedColorF

           barCode:RotationAngle := 0

           barCode:CodeBinaryData := Encoding.Default.GetBytes(barCode:CodeText)

           barCode:Options.QRCode.CompactionMode := QRCodeCompactionMode.Byte

           barCode:Options.QRCode.ErrorLevel := QRCodeErrorLevel.Q

           barCode:Options.QRCode.ShowCodeText := true

           barCode:DpiX := 144

           barCode:DpiY := 144

          self:image:DataContext := ToBitmapImage(barCode:BarCodeImage)

      return

      public static method ToBitmapImage(bitmap as Bitmap) as BitmapImage

          var memory := MemoryStream{}

           bitmap:Save(memory, ImageFormat.Bmp)

           memory:Position := 0

          var bitmapImage := BitmapImage{}

           bitmapImage:BeginInit()

           bitmapImage:StreamSource := memory

           bitmapImage:CacheOption := BitmapCacheOption.OnLoad

           bitmapImage:EndInit()

           bitmapImage:Freeze()

      return bitmapImage

  end class

   [ValueConversion(typeof(SolidColorBrush), typeof(SolidColorBrush))];

  class ColorChange implements IValueConverter

      virtual method Convert(value as object, targetType as Type, ;

                   parameter as object, culture as CultureInfo) as object

          try

              var myBrush := (SolidColorBrush)value

              var InputColorA := (int)myBrush:Color:A

              var InputColorR := (int)myBrush:Color:R

              var InputColorG := (int)myBrush:Color:G

              var InputColorB := (int)myBrush:Color:B

              var InputColorSum := InputColorR+InputColorG+InputColorB

              if InputColorSum > 450

                  return SolidColorBrush{Colors.Black}

              else

                  return SolidColorBrush{Colors.White}

              endif

          catch oEX as SystemException

              return SolidColorBrush{Colors.White}

          end try

          //return DependencyProperty.UnsetValue

      return SolidColorBrush{Colors.Red}

      virtual method ConvertBack(value as object, targetType as Type, ;

                   parameter as object, culture as CultureInfo) as object

          //return DependencyProperty.UnsetValue

      return SolidColorBrush{Colors.LimeGreen}

  end class

  public class ColorNameValue

      public property ColorName as string auto

      public property ColorValue as int auto

      public constructor()

          self:ColorName := ""

          self:ColorValue := 0

      return

      public constructor(cName as string, nValue as int)

          self:ColorName := cName

          self:ColorValue := nValue

      return

  end class

END NAMESPACE

===============================================================================

<Window x:Class="WpfCreateQRcodeXS.WPFWindow1"

      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

      xmlns:converter="clr-namespace:WpfCreateQRcodeXS"

      Title="WPF : X# : QR code creation :" Height="420" Width="800"

      Name="QRwindow" Background="Bisque">

  <Window.Resources>

        <converter:ColorChange x:Key="myColorChange" />

  </Window.Resources>

<!---->

  <Grid>

      <Grid.Resources>

          <Style TargetType="ComboBoxItem">

              <Setter Property="Background" Value="{Binding RelativeSource={RelativeSource Self}, Path=Content}"/>

              <!--<Setter Property="Foreground" Value="Red" /> simple assignment -->

              <Setter Property="Foreground" Value="{Binding RelativeSource={RelativeSource Self}, Path=Background,

                      Converter={StaticResource myColorChange}}" />

              <!-- above works OK! -->

          </Style>

      </Grid.Resources>

      <Button x:Name="button" Content="QR create !! - Cologne 2018"

              Margin="10,-1,0,0" VerticalAlignment="Top" Height="25"

              Background="Salmon" Click="button_Click" FontStyle="Italic" FontWeight="Bold"

              ToolTip="Click this button to CREATE code" HorizontalAlignment="Left" Width="192"/>

      <Image x:Name="image" Margin="10,126,10,10" Source="{Binding Mode=OneWay}" Stretch="Fill"

            ToolTip="Drag the Form to re-size the QR code."/>

      <TextBox x:Name="textBox" HorizontalAlignment="Left" Height="56" Margin="10,61,0,0"

              Text="{Binding Path=QRtext, ElementName=QRwindow, Mode=TwoWay,

                              UpdateSourceTrigger=PropertyChanged}"

              VerticalAlignment="Top" Width="300" TextWrapping="Wrap"

              Background="LightGoldenrodYellow" Foreground="Black"/>

      <TextBlock x:Name="textBlock" Margin="318,91,10,0" Height="24"

                Text="{Binding Path=QRtext, ElementName=QRwindow, UpdateSourceTrigger=PropertyChanged}"

                VerticalAlignment="Top" TextWrapping="NoWrap" Background="Honeydew"

                Foreground="Red" ToolTip="Just a copy of the bound string data."/>

      <Label x:Name="label" Content="QR code : Change / Edit / Input :" HorizontalAlignment="Left"

            Margin="10,30,0,0" VerticalAlignment="Top" Width="300"

            Background="LightBlue" FontWeight="Bold" FontSize="16"/>

      <ComboBox x:Name="comboBoxF" HorizontalAlignment="Right" Margin="0,61,150,0"

                VerticalAlignment="Top" Width="120" ItemsSource="{Binding}"

                SelectedItem="{Binding Path=SelectedColorF, ElementName=QRwindow, Mode=TwoWay,

                                      UpdateSourceTrigger=PropertyChanged}"/>

      <Label x:Name="label1" Content="Select Fore Color :" HorizontalAlignment="Right"

            Margin="0,30,150,0" VerticalAlignment="Top" Width="120" FontWeight="Bold"

            Background="LightGreen"/>

      <ComboBox x:Name="comboBoxB" HorizontalAlignment="Right" Margin="0,61,12,0"

                VerticalAlignment="Top" Width="120" ItemsSource="{Binding}"

                SelectedItem="{Binding Path=SelectedColorB, ElementName=QRwindow, Mode=TwoWay,

                                      UpdateSourceTrigger=PropertyChanged}"/>

      <Label x:Name="label2" Content="Select Back Color :" HorizontalAlignment="Right"

            Margin="0,31,12,0" VerticalAlignment="Top" Width="120" FontWeight="Bold"

            Background="SteelBlue" Foreground="Yellow"/>

  </Grid>

</Window>

===============================================================================

============================================

===============================================================================

===============================================================================

PHH - 20th. February 2018.


Syncfusion Reporting

You may have heard of a good offer - no its a GREAT offer - from Syncfusion.

Recently they have made their software tools available to small enterprises for no cost - yes, FREE.

Since we as developers would not try and live by giving our applications away for no cost, I can't

work out why some guys are 'mad' about FREE !?

I think quality, ease of use, and wasting as little of my time as possible are better criteria for choosing

a support Framework - for Reporting or anything else.

So if we can find a suitable third party framework for reporting - from a quality software house - and it is

also on offer to us as a free licence, then maybe we should give it a good try.

*** CARE *** just bare in mind that an offer of a free licence today does NOT mean it will be free tomorrow.

The world can change over night. Remember the free Reflector of old, that we now pay reasonable sums for ?

And the initial provider of Reflector gave it to a third party on the public knowledge that it would be always

free to mankind ;-0((

So tread carefully. Once we get sucked or drawn into using a framework it is not easy to simply change to

another over night !!!

Enough of this, and lets take the offer at face value, and have a look at what is on offer.

https://www.syncfusion.com/company/about-us/news/press-releases/2015/rknc—jerelease2015-01-13/syncfusion-essential-studio-enterprise-edition-now-free-to-individual-and-small-business-developers

Here is a search result I got which led me to the free download :-

==========================================================================================

https://www.syncfusion.com/products/communitylicense

Lets move on a pace or two ..............

First some general information available on the web :-

==================================================================================================================================

========================================================================================================================================================

======================================================================================================================================================

==============================================================================================================================================

PHH - 30th. December 2017.


The 'Community Licence' ...

https://www.syncfusion.com/company/about-us/news/press-releases/2015/rknc—jerelease2015-01-13/syncfusion-essential-studio-enterprise-edition-now-free-to-individual-and-small-business-developers

=================================================================================================

==========================================================================================================================================

=======================================================================================================================================

*** CARE *** - less than 5 developers to me means those with four, maximum.

================================================================================================================================

==============================================================================================================================

================================================================================================================================

*** CARE *** I can't see any mention of any support time !? To me this is rather crucial as I think many

may need it for such a complex product.

We need to find out more.

PHH - 1st February 2018.


Is it right for us XSharp guys ? ....

Okay then folks, lets be quite honest and open with ourselves.

Even if a product id quality, and free (support included or not) we need to make a good effort

to checkout whether it will be easy for us X# guys to use, and also fit our needs when it

comes to us integrating it with our business applications - desktop types that is.

*** NOTE *** Any cross-check we list / produce here, will need to be applied equally to all the

other possibilities of Reporting products from third party companies.

- We need to think of our colleagues coming from DBFs - still using them !?

So we need a reporting solution which is 'strong' on using in-memory objects for report.

- We need to cover all printing scenarios - from a simple page of text and graphical items, not

bound at all to any data, through to more complex reports bound to data sets returned from

back-end queries, and where grouping and sub-group and column summation takes place.

And probably much more too, on the colour graphics side of things.

- We need to be able to save report definitions, and review them locally and over a local

network.

- We need to be careful that we don't get a fancy and powerful solution which does cross-platform

stuff very well, and displays reports in a Browser mode, yet sacrificing some of the simpler stuff,

which to my mind is crucial to have. We MUST have a printing solution.

- If it doesn't easily produce an impressive "Good morning Universe" sheet then pass-on by!

PHH - 1st February 2018.


Infragistics Reporting

Infragistics are one of the top six (or less) companies producing .NET GUI tools and related

reporting tools.

You can find then here :-

===========================================================================================

And you get drawn into their colourful and graphical world via this intro screen :-

========================================================================================================================================================================

Yes, yet another 'Developer Toolkit' for us to master and use ;-0)

=================================================================================================================================================================================

It has to be said that their tools are top class, first quality, and therefore not cheap - but them we would expect that.

The big drawback for me (and my needs) is that the Reporting 'system' seems to be 'outside' of the .NET world we

as XSharp developers would possibly / probably wish to use.

Lets see another nice graphic :-

=====================================================================================================================================================================

Back to the drawbacks, or should we say 'Dashboards'.

Here below is a list which when read by guys like me say - why all dashboards ? Where is

the code and tool link into my .NET apps ?

Read the details shown next and then we will get into some text for real :-

================================================

And here is my 'real' text - that which sounds the alarm bells for me and my needs.

Checkout these details in full :-

==================================================================================================================================

=============================================================================================================================

And here is some pretty stuff built around displaying the Dashboards :-

==============================================================================================================================

PHH - 19th. January 2018.


Stimulsoft Reporting

https://www.stimulsoft.com/en/products/reports-wpf

=================================================================================================================================================================

=================================================================================================================================================================

=================================================================================================================================================================

=================================================================================================================================================================

=================================================================================================================================================================

=================================================================================================================================================================

=================================================================================================================================================================

=================================================================================================================================================================

=================================================================================================================================================================

PHH - 30th. & 31st. December 2017.


WinWard Reporting

http://www.windward.net/products/engine/

====================================================================================================================================================

PHH - 30th. December 2017.


Telerik Reporting

Generally reckoned to be one of the top three or four products in the Reporting arena - along with

Infragistics and DevExpress.

https://www.telerik.com/products/reporting.aspx

==========================================================================================================================

PHH - 30th. December 2017.

PHH - 31st. December 2017.


Seal Reporting

http://www.sealreport.org/

=====================================================================================================================================================

==========================================================================================================================================================

PHH - 1st. January 2018.


Qlik Reporting

https://www.qlik.com/us/products/qlik-sense

===========================================================================================================================================================================================================

=========================================================================================================================================================================================

PHH - 30th. December 2017.


Navigation: 'Printing and Printers', and Report generation too ... > Third Party report generators for .NET >

Return to chapter overview

Crystal Reports

http://www.crystalreports.co.uk/

PHH - 30th. December 2017.


fyiReporting ...

Our good friend and colleague Fabrice Foray pointed me at this reporting 'option'.

After reading the following web extra from :-

https://github.com/majorsilence/My-FyiReporting

I am not sure that it is an option that I personally would follow up.

There seems to be a few phrases, words or sentences, which start to alarm me.

My MAIN concern is that selecting a reporting / printing solution because it is FREE is certainly NOT the

way I would advise.

Talk below of projects 'dying', as well as everything being based on the RDL approach in Reporting Services of Microsoft,

is not what I wish to read in documentation of a new Reporting approach for .NET and XSharp.

See for your self in the following image details :-

=================================================================================================================================================

Its up to you ;-0)

Time to move on .....................

PHH - 2nd. January 2018.


"ReportPro" - 2 and 3

Many VO guys have been using 'ReportPro' for years, if not decades.

There was version 2 and 3, and developers have invested time in designing reports for these versions.

Currently Robert at XSharp has published details in the Forum of some future 'Life' and development

of ReportPro, with it being on sale to the X# community.

I have to confess that I have not used it but maybe I should include details here, and even get a copy

for me to try out and write some eNotes about it too.

I may need to turn to some of you guys for help with this mini-project.

*** >> Update - Mike and Tim are on my case, helping me that is << ***

So far I have a couple of RP3 apps which launch and run.

=====================================================================================================

The information I have from Mike is that the RPT file format used is a proprietary one, meaning it seems

to be specific to ReportPro.

If indeed this is correct then we will need to take the report file (' .RPT' ) into account when formulating

an approach to using RP3, or not.

Our whole approach to what we use in the .NET world should be base around future flexibility of third party

'stuff' and NOT digging ourselves into holes. No matter how nice, or good, the third party tool has been.

No guy coming to programming now (in 2018) would choose to have a data file solution of DBFs. It would

just make no sense at all. BUT, there are many working solutions out there still using DBFs quite effectively.

For this reason we need a printing and report solution which caters for DBF applications.

*** DON'T forget *** we can use more than one reporting solution in one application if we so choose.

We could use RP3 to use and preview older RPT files, as well as using DevExpress (for example) to create

and preview current and more modern report files.

Lets now move on to the next sub-section where we will have a first look at RP3, and see what we may do

with it, once we have a suitable data source to play around with.

See you there ..............

Oh! - in the next sub-section called "A First look ...." we will go right through the process of making a simple

report, from having nothing, to doing a quick preview of the data in the simple report.

In the sections after that we will look in more depth at each of the parts of designing and viewing a report.

Lets move on ................

PHH - 27th. March and 10th. April 2018.


A first look ...

As you can see in the following image, we seem to have four application files from which to choose.

Here are the third party 'tools' as such :-

=====================================================================================================

It is very tempting at first to look towards the 'Design' app for a first try :-

I tried this :-

Followed by this :-

And then this (all seemed to be going as expected) :-

However, the next step of selecting a data source proved to be a 'show stopper'.

The Data Source tab seemed to give me no workable choices ;-0)

So, I decided to start again but this time with the 'ReportPro3' application see below :-

=====================================================================================================

So I started again as can be seen below :-

===============================================================================

And since we are just finding our feet with this report solution, I chose a standard type of report as

well as asking for the help of the Wizard.

Now we can type in a title for our report :-

==============================================================================================

And now when we use the tab for 'Data Source' we seem to find some functionality :-

==============================================================================================

From the currently available choices of database, I have selected Foxpro (CDX) as it seems to be the

one to go with the DBF files I have found in my old folder system.

Here is what I got on screen next :-

And so I selected the 'Customers' file, which brought the following on screen :-

====================================================

Next I explored a little bit, by clicking for the context menu with the right hand mouse button.

Although I do not use any of the following menu options, we need to know what is there for the selected

file. These all seem to be options related to the selected file itself, rather than any table column options.

Check this out :-

=======================================================================

Okay then folks, now lets use the 'Fields' tab option to see what that has to offer us :-

================================================================================

And just for the sake of a full test, lets now select the whole set of the Fields/Columns :-

================================================================================

It should be pretty obvious to experienced IT coder guys that the left and right arrow

buttons move selected fields backwards and forwards between the selection boxes.

Moving on to the next tab we see some choices for 'Sorting' :-

================================================================================

I have chosen to select by Company descending (Z- A) and Postcode ascending (A-Z).

I will leave it for you to play around with such choices and selections.

The next tab is for grouping - BUT - for a first try at report design I feel we can leave

grouping for the moment.

================================================================================

So we can move on to the subsequent (next) tab for filtering :-

==============================================================================

We need to click the plus ( ' + ' ) sign to add a filtering condition :-

==============================================================================

There are some 'active' functionalities built within the Field and Operator columns, which

help us specify what we may wish for, but the Value column seemed to be a type it in column.

I had a look at "Page Layout" and Style - these are similar to the following :-

Changing it to :-

======================================================

Now "Style" :-

================================================================================

Okay then folks, now that we have finished with the tabs and their input of details, lets

now try to save it :-

=====================================================================================

Supplying a name and placing the RPT file in the correct folder :-

Okay then, so far in our simple report test we have created an RPT file - so what does it look like ?

Well, lets try to launch and run the application named "ReportPro3.ReportView" - and here it is :-

==============================================================================

So we need to input, select and open the RPT file we just made. Check this out below :-

===========================================================================================

Now we see the following once the file is opened :-

===================================================================================

And when I explore the tree structure a little, we see this :-

===================================================================================

And when clicking (pressing) the all important 'Preview' button we get this below :-

===========================================================================================================================================================

Yes, I know, its not very pretty or colourful ;-0) But it is page one and we do get a final page as shown below (page 7) :-

==============================================================================================================================================

=========================================================================================================================================

========================================================================================================================================================================

==============================================================================================================================================================================

===============================================================================================================================================================

=====================================================================================================

PHH - 10th April 2018.


Navigation: 'Printing and Printers', and Report generation too ... > Third Party report generators for .NET > "ReportPro" - 2 and 3 >

Return to chapter overview

Choosing a 'Data Source' ...

PHH - 10th April 2018.


Navigation: 'Printing and Printers', and Report generation too ... > Third Party report generators for .NET > "ReportPro" - 2 and 3 >

Return to chapter overview

Designing a report

PHH - 10th April 2018.


Navigation: 'Printing and Printers', and Report generation too ... > Third Party report generators for .NET > "ReportPro" - 2 and 3 >

Return to chapter overview

Viewing an existing Report ...

PHH - 10th April 2018.


Navigation: 'Printing and Printers', and Report generation too ... > Third Party report generators for .NET > "ReportPro" - 2 and 3 >

Return to chapter overview

Previewing ...

PHH - 10th April 2018.


Navigation: »No topics above this level«

Return to chapter overview

New technology applications

PHH - April 11th. 2017


Navigation: New technology applications >

Return to chapter overview

Universal

PHH - April 11th. 2017


Navigation: New technology applications >

Return to chapter overview

Cross-Platform

PHH - April 11th. 2017


Navigation: New technology applications >

Return to chapter overview

.NET Core

PHH - April 11th. 2017


Navigation: New technology applications >

Return to chapter overview

Android

PHH - April 11th. 2017


Navigation: New technology applications >

Return to chapter overview

Cloud

PHH - April 11th. 2017


Navigation: New technology applications >

Return to chapter overview

Web

PHH - April 11th. 2017


Navigation: »No topics above this level«

Return to chapter overview

Moving to X# from ....

PHH - April 12th. 2017


Navigation: Moving to X# from .... >

Return to chapter overview

coming from Visual Objects

PHH - April 12th. 2017


Navigation: Moving to X# from .... >

Return to chapter overview

coming from Vulcan.NET

PHH - April 12th. 2017


Navigation: Moving to X# from .... >

Return to chapter overview

coming from (x)Harbour

PHH - April 12th. 2017


Navigation: Moving to X# from .... >

Return to chapter overview

coming from Visual FoxPro

PHH - April 12th. 2017


Navigation: Moving to X# from .... >

Return to chapter overview

coming from Clipper

PHH - April 12th. 2017


Navigation: »No topics above this level«

Return to chapter overview

Using the XPorter tool

PHH - April 12th. 2017


Navigation: Using the XPorter tool >

Return to chapter overview

For existing Vulcan apps

PHH - April 12th. 2017


Navigation: Using the XPorter tool >

Return to chapter overview

For existing Vulcan / C# hybrid apps

PHH - April 12th. 2017


Navigation: »No topics above this level«

Return to chapter overview

CSV data and files, 'How To'

PHH - 24th. October 2017


Navigation: CSV data and files, 'How To' >

Return to chapter overview

how is CSV defined - Wikipedia

PHH - 24th. October 2017


Navigation: CSV data and files, 'How To' >

Return to chapter overview

What we as developers need to know about CSV

PHH - 26th. October 2017


Excel and our CSV data files

================================================================================================================================================================================================

====================================================================================================================================================================

Check the image above. So what is the problem ?

PHH - 25th. October 2017


Navigation: CSV data and files, 'How To' >

Return to chapter overview

Use of strings as opposed to characters

PHH - 24th. October 2017


Escape sequences : some ways to make things easier

PHH - 24th. October 2017


X# .NET strings : StreamWriter & StreamReader

PHH - 24th. October 2017


Sample application - 'Address Book'

We will follow through the creation and testing of a simple "Address Book" system.

The application will allow input and editing of Address Book Entry objects (items).

A main point of the app is to allow easy printing of address lists and summaries.

There will be an option for full details for each address book 'entry', as well as easy

ways to promote filtering and sorting using LINQ technology.

PHH - 24th. October 2017


Navigation: CSV data and files, 'How To' > Sample application - 'Address Book' >

Return to chapter overview

Specification of the application

PHH - 26th. October 2017


Navigation: CSV data and files, 'How To' > Sample application - 'Address Book' >

Return to chapter overview

The definition of the 'AddressBookEntry' class

PHH - 24th. October 2017


Encoding and Decoding

As we know, CSV data is stored in simple files with each record being a single line (usually), and

each data field in the line record is separated with a comma.

For .NET a record usually represents an object, and the fields the values of the object properties.

For an instantiated object, processing the Properties and placing their values into a long CSV string,

or line, is called 'Encoding'.

The reverse, or opposite, process is where we start with a read-in long string and the process this

to find values which we then associate with object properties. The process of moving from file to

many strings, and the to object properties is termed de-coding.

Here is the two line/record of an encoded file :-

=====================================================================================================

Note in the above image all the fields have their individual data surrounded by double quotes.

This was my choice to make things easier for my application coding.

And now we need to have a brief look at the decoded data as it was assigned to property values.

================================================================================

I will explain what I did with the above code and data.

PHH - 24th. October 2017


Navigation: CSV data and files, 'How To' > Sample application - 'Address Book' > Encoding and Decoding >

Return to chapter overview

Decoding - file reading and subsequent processing

PHH - 24th. October 2017


Navigation: CSV data and files, 'How To' > Sample application - 'Address Book' > Encoding and Decoding >

Return to chapter overview

Encoding - processing ABE property values to make a long string

PHH - 24th. October 2017


Navigation: CSV data and files, 'How To' > Sample application - 'Address Book' >

Return to chapter overview

Automatic 'backup' - data fileon disk

PHH - 26th. October 2017


Navigation: CSV data and files, 'How To' > Sample application - 'Address Book' >

Return to chapter overview

Working code and XAML script.

PHH - 24th. October 2017


Appendix 'A' - ILSpy

http://www.softpedia.com/get/Programming/Debuggers-Decompilers-Dissasemblers/ILSpy.shtml

===================================================

Browse and analyze assemblies by using this .NET application decompiler that features support for DLL, EXE and Windows Metadata files

ILSpy is an open source decompiler and assembly browser that can manipulate .NET packages in the following formats: DLL, EXE, and WINMD (Windows Metadata).

As an open-source application, ILSpy consists of a set of binaries, as well as a separate source code package that can be modified without restrictions by developers in order to build other software on top of it.

The binaries are brought inside a portable archive that doesnâ€™t need installation. It can be transferred to a removable drive and launched from there on any computer.

In terms of appearance, the program sports simple, yet intuitive looks. Most of the area inside the main window is allocated to the assembly resources, which can be navigated as soon as you load the source file.

ILSpy can render and decompile code in three different ways: C#, Visual Basic, and IL disassembly. You can make the selection of the default decompilation method using the dedicated drop-down menu and you are able to switch between them with the effects almost instantaneously visible.

The operations youâ€™ll be allowed to perform include a search function for methods and properties, navigation history, the possibility to export resources separately, a BAML to XAML decompiler, as well as the option to save the whole project to CS format.

In addition to all of these, ILSpy sports the advantage of extensibility, which means further functionality can be implemented via plugins.

On an ending note, ILSpy comes across as a steady and reliable NET decompiler that can also be used to browse assemblies on the go. It could use native support for editing assemblies, but we understand that this feature is on the to-do list of the developer, so it will be implemented at some point in the future.

======================================================================================

===============================================================================================================================================

=============================================================================================================================

PHH - 19th. January 2018.


Navigation: »No topics above this level«

Return to chapter overview

Appendix 'B' - 'Reflector' from Red-gate.

PHH - 19th. January 2018.


Appendix 'C' - Repairing Visual Studio, and settings ...

PHH - 27th. January 2018.


Navigation: Appendix 'C' - Repairing Visual Studio, and settings ... >

Return to chapter overview

Full app 'Repair' ...

PHH - 27th. January 2018.


Navigation: Appendix 'C' - Repairing Visual Studio, and settings ... >

Return to chapter overview

Working with the 'Toolbox' ...

PHH - 27th. January 2018.


Navigation: Appendix 'C' - Repairing Visual Studio, and settings ... >

Return to chapter overview

Updating Help information ...

PHH - 27th. January 2018.


Navigation: Appendix 'C' - Repairing Visual Studio, and settings ... >

Return to chapter overview

Cosing, Opening, and Moving the Panes/Windows ...

PHH - 27th. January 2018.


Appendix 'D' - generally working within Visual Studio ...

Well, here is a start to a complex operation - working with VS is not easy until we move up the

steep learning curve and pick up a few dood tips, tricks, and basic operational knowledge.

Here is a useful tip which I use regularly while coding, and every day as well.

Moving through the editors, usually backwards for me, we can call this 'Navigation'.

Suppose I am in an editor for an X# project and am translating a working C# project.

I need to copy some code detail from the C# file to the newer X# file (PRG).

So from the cursor position in the X# file I select to move to the .CS file in the C# project.

There I find the code required and highlight it and copy it to the clipboard.

NOW then, I simply hold down 'Ctrl' and press the minus ( '-' ) key and my cursor is back

where I wish it to be. BINGO, or 'Magic' !

Here is some supporting information :-

https://blogs.msdn.microsoft.com/zainnab/2010/03/01/navigate-backward-and-navigate-forward

=============================================================================

*** NOTE *** this is easier to 'DO' than it is to read about technically !

===============================================================================================================================

============================================================================================================================

Moving on .....................

PHH - 27th. January 2018.