Skip to content

Creating your first map

Leonardo Pessoa edited this page Jul 8, 2022 · 3 revisions

I've reused the very same name that CSP called the screens of the application when designing the class to represent one: a Map. Thus, Terminal applications may consist in one or more maps that will be show or hidden during the execution of the application.

In order to create a map, all you have to do is create a subclass inheriting from Map, no need for any fancy code here. When you create a new instance of your Map subclass, it will automatically try and load the screen definition from a file with the same name as the full name of the map class plus the extension .map. Basically, if you place your .map file together with its Map class file, you will be safe. So, for example, if you have a class called Coolreads.BookInfoMap, the map will try to load an embedded resource called Coolreads.BookInfoMap.map on the same namespace. Also remember the .map file must be built as an embedded resource into the application itself and while at it remember to account for the default namespace of your application as it accounts for the name of the file.

In case you want to load your map from a different source, assembly or file, you can set the map contents using the Contents property (yes, you could even load it remotely if you wanted but I wouldn't really recommend it):

public class BookInfoMap : Map {

   public BookInfoMap() {
      Contents = LoadFileFromAnotherSource();
   }
}

The Contents property is write-only and can only be set during the map class initialisation (i.e. its constructor).

What makes a map

Now that we have our application in place and a class to make reference to our map, all we have to do is actually draw the screen.

For that, as I mentioned on the previous section, you can set the value of the Contents property or create an embedded file with the same full name of your map class with the extension .map. Here is an example from a possible library application (any similarities with the same page on a certain book reading social network are mere coincidence ;) ):

>
>  coolreads                      ADD A NEW BOOK
> 
>       TITLE: ¬XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
>  SORT TITLE: ¬XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
>      AUTHOR: ¬XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
>        ISBN: ¬999999999999           ASIN:¬A999999999
>   PUBLISHER: ¬XXXXXXXXXXXXXXXXXXXXXXXXXXX
>   PUBLISHED YEAR: ¬999  MONTH:¬9
>       PAGES: ¬999
>      FORMAT: ¬ PAPERBACK   ¬ HARDCOVER   ¬ E-BOOK
>     EDITION: ¬9 ¬XXXXXXXXXXXXXXXXXXXXXXXX
> DESCRIPTION: ¬XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
>              ¬XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
>              ¬XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
>              ¬XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
>              ¬XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
>              ¬XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
>    LANGUAGE: ¬XXXXXXXXXXXXXXXXXXX
> 
>  ____________________________________________________________________________ 
>  ¬XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
>  PF1 = HELP   PF3 = RETURN   PF10 = SAVE

As you can see, you get a pretty close view of how the screen will look like when rendered to the console. Note only the greater than symbol (>) on the leftmost side of the line, it is there to indicates this is what will be shown when your map is loaded to the screen (sans the >). Any lines with almost any other character at this position will be completely ignored but you will understand why we need this in a minute, just follow along for now.

Apart from the screen visible in plain text, the second thing you might have noticed in our map file is all those spaces filled with Xs and some 9s (there is an A there too; have you found it?). Although those are completely unnecessary, it may be good practice to place them in your map in order to help outlining how big your fields and labels will be. The only mark actually needed it the negate symbol (¬, usually obtained by pressing RIGHT_ALT+6) as they work as placeholders for our fields, the actual size of the field will be defined later in this same file. Once you get a map to display, you will notice none of those symbols themselves are printed and are replaced by a regular whitespace, even when the field is not shown or has no contents

Defining fields

Once those placeholders are in-place in our map, the next step is to define what will be shown in them and these must be defined in the same order their placeholders appear on the map. To simplify, since originally there was only one kind of content, we call a field anything that can be used for interaction with the user in a map. Fields are defined in their own lines in the map in lines beginning with the negate symbol (¬) followed by the field arguments, which are usually very simple:

  • By default any field that does not specify its type is a regular text input field, its argument is a mask used to validate the values input by the user
    • Xs mean any given character is an acceptable input value;
    • As mean only Latin alphabet letters (i.e. A through Z) are acceptable;
    • 9s mean only digits (i.e. 0 through 9) are acceptable;
    • *s are a special case that accepts any character but any mask starting with it means that is a password field;
    • Other characters are permitted on the mask meaning only that character is acceptable at that position.
  • Every other kind of field must be prefixed with the type of field using a colon (i.e. <type>:<args>)
  • Types are defined using always exactly three Latin alphabet letters and the usage of arguments vary depending on the type
    • ROT indicates a label (read-only text). Its argument may be a number indicating the size of the field or a text whose length will be used instead;
    • MSG is a special status label that can be used to convey coloured information about the status of the application, only one can be present in each map;
    • CHK is a single character text field that works as a check box accepting only X and / as input, any arguments given to it are ignored;
    • SEL is similar to CHK but takes a number as argument and forms a group of exclusive choice boxes (i.e. only one can be selected at any given moment);

On a map like our example, this would look a lot like repeating itself and there is no direct relationship between the field placeholder and the field definition, so it is easy to get lost on a big map. For reference, the declaration of the fields of our example maps would look like this:

¬XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
¬XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
¬XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
¬9999999999999
¬A9999999999
¬XXXXXXXXXXXXXXXXXXXXXXXXXXXX
¬9999
¬99
¬9999
¬SEL:1
¬SEL:1
¬SEL:1
¬99
¬XXXXXXXXXXXXXXXXXXXXXXXXX
¬XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
¬XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
¬XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
¬XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
¬XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
¬XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
¬XXXXXXXXXXXXXXXXXXXX
¬XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Fields in a map can be declared anywhere, at the top of the file, on the bottom, or even right below the line where the field is placed. However, I wouldn't recommend this last approach. It may seem easier to account for the fields in a map but it also can create great separation between the lines of the map, making it difficult to see how the map will look like on the screen.

Only fields matching a placeholder and a declaration are displayed, i.e., if you declare more fields than you place on the map, the extra will be ignored (thus it is a good place to look for if a field you declared is not being show). Similarly, if you place more placeholders on your map than you declared fields, the extra placeholders will be ignored. Also notice that any content on a map that overlaps with a field will be stripped and never shown to the user, not even if the field is hidden.

Showing the map

I know you will not believe me but that's it, you have a functional screen for your application and did not write a single line of code yet. To show you that, all we must do is make the application we created previously show our map in either of the means shown in Creating the Application:

Application.Run(new BookInfoMap());

Go ahead, run it. I'll wait right here. Amazingly simple isn't it.

Of course most applications are not made using a single map and you might easily find yourself wanting to show more and more of them as the application grows and only the initial screen of your application is shown using the Application. But showing another map after the application is running is even easier: instantiate the map as before and call the method Show() in it. It does not matter how you called Run() in your application, the map will resolve.

BookInfoHelpMap help = new();
help.Show();

When a new map is shown, the previous map does not simply dies. Instead, the Application< class automatically keeps track of each open map and allows you to navigate all the way back to the first map by just calling Close() on the current map (notice you can only close the map that is the current one being displayed). The application ends when the first map on this stack is closed.

Now you might be wondering why do I require you to instantiate the map class? I could have easily done it using reflection and/or dependency inversion techniques but by having you instantiate it yourself makes the code simpler and allows you to decide what is the best way to do it (design patterns involved or not). Also, that will enable you to keep track and pass data between maps with ease.

Navigating between fields

Once you get your first map on the screen the first thing you might wonder is how you get to move between fields. That can mostly be done with the TAB and arrow keys. While the arrow keys will allow you to move freely through the screen, the TAB key will move you straight to the next visible and editable field (labels are not editable).

Some old console applications used to allow navigating through fields using the ENTER key. We chose not to hardcode this in the framework because ENTER might mean different things in the view of different people, organisations or applications, but is also quite simple to implement if necessary (however I'll get to it later).

Clone this wiki locally