Graphical User Interfaces - A Complete Study
Part 3 - ErgonOS Organization and Standards
Written by Stéphane Richard (Mystikshadows)
INTRODUCTION:
If you're reading this, I'm assuming you've read Part One and Part Two of my series already. If you didn't, please do before continuing here as the first two parts will greatly clarify what is going on in this third part of the series. In this document we'll begin to cover many aspects of what the programming will comprise. When starting to code a project this size, it's important to document yourself all the way. And this series will do just that.
Even though technically I will be working by myself on ErgonOS, one never knows what the future holds. I could be offered help in this project, I could let it go for a while and get back to it afterwards, I might even work for a good while on one part of the GUI and forget some parts that I created a while back when it's time to get back to those other modules. These are all situations where quality of code and effort in code documentation and "intelligent" organization of modules, a steady global naming convention and such type of tools can really come in handy. So before any coding begins, we'll defined these standards, and you'll see that the code I'll be writing will comply to that standard. So let's start this big wheel turning shall we?
ERGONOS CODING STANDARDS:
The standard I'll be using is broken down into three workable standard groups. These are the "Source File Documentation", "Indentation and Spacing" and "Naming Conventions". As I mentionned, coding standards are there because when the code gets big, it's clarity will have better chances at staying as clear as possible across the thousands of lines of codes there will be. Let's take the time to review these standards right here.
- Source File Documentation:
When creating a source file, often, documentation of this source file is quickly neglected usually because of lack of time, some think time is better spent actually coding the intended purpose and say to themselves: "I'll document it when it works!". I myself often used that technique as well because when there's no time there's no point. However, this project has no deadlines. Like everyone else, I want my things done yesterday but this is suicidal on a project this big. I'd rather wait (as impatient as I am), as long as it takes, to be sure that what I get is both readable and understandable. As such I'll be making a special effort to comply with the following standards.
- Module Level comments:
This is usually done at the beginning of a module, it should have the module name, project to which it belongs, version number, creation date, author name, and other standard information of this nature. It should also have a paragraph that explains it's purpose clearly. It could be a sentence, sometimes a sentence is enough, other times more information is needed. Common sense will play a role to determine how much information is needed. Right after this I leave a comment section to add "things to do" so I don't lose track of thing or forget something that should have been done. Finally, at the end of the module, I also like to have a history section, at the end of the source module, so that I can track the work done. - Procedure Level comments:
This is usually done at the beginning of a procedure or function. It should have the name of the function or procedure, a list of parameters and their types and an indication on whether a parameter is optional or defaulted to a value somehow. It should state what the function returns, if any, what it assumes, what valid values are acceptable for parameters in the case of dates for example, perhaps a date can't be earlier than a certain date. And a paragraph on the role of the procedure or function. - Source Level comments:
This is done just before a certain block of code within a procedure or function. usually one or two sentences to explain the code to come when the developer knows it's a tricky part to understand. Again, logic and common sense should guide this level of documentation, some code are clear enough as is, some require some explanation.
- Module Level comments:
- Indentation and Spacing:
Indentation, to me, helps to clearly determine where a block of code begins and where it ends. For the number of spaces to use, that's irrelevent, whatever the preference may be just as long as it's clear enough to visually see the beginning and end of a language contruct. I believe at least 2 characters should be used, but aside that, any indentation style is left up to the developer. Me I usually align to the next Item. like so:
The main reason for this is that to me Aligning the CALL ThisSub under ThisVariable and ThatSub under Counter like I did is visually cleaner in my opinion. That's just my opinion however and if I was to receive code that wasn't aligned this way I wouldn't refuse it. Everyone has their own style of coding. Me I just happen to like it that way.IF ThisVariable = 1 THEN CALL ThisSub END IF FOR Counter = 1 TO 10 CALL ThatSub NEXT Counter
Usually indentation is used for Class definitions, If Then Else constructs, Loops, Procedure and Function declaration and the rest of the code is then indented to the level where it should be to visually show that it is part of a given construct. Many editors even have an Auto-Indent feature that can do a good job at indenting code for your in a more than acceptable means. In other cases, most language have a "code beautifier" or code formatter program that can take a source file and spruce it up visually speaking.
Common sense is really what it's all about here. If the code looks clear enough to understand then it should be perfect just the way it is. It's all visual aids that help other understand the source file, so it should be used as a documentation tool.
- Naming Conventions:
As far as naming conventions goes, well common sense and logic will play a very big role here as well. When naming a source file, a procedure or function, a class definition or a variable, one has to think of others that haven't seen the code yet. What can they say about the names you've chosen determines the quality of the naming convention used.
- File Names:
When naming a source file, a form, a database, whatever the case may be, it's important to keep the name of the file as clear as possible as far as it's intended purpose is concerned. - Class Names:
Well a class is an object and therefore should be named according to how you would name any other objects. If you are defining a class for animals for example, simply call it Animal, it needs to speak for itself. Common sense should once again help. - Procedure/Function Names:
A procedure or a function usually perform an action or a calculation, therefore, it is recommended that the procedure or function name start with a verb stating that action followed by a name of what that action is perform on. For example a function that would caculate the tax on a given amount should be called CalcTax or CalculateTax. - Variable and DataType Names:
A variable stores information to be used later or elsewhere in the module or a given project (depending on their scope). A name, aside standard variable naming conventions (start with a letter, followed by numbers or other letters etc etc..) should tell, by its name, what information it will store at the very least for the sake of clarity of code. A DataType (User Defined Type that is) is a structure that will more than one piece of information It's name will need to represent the data it will hold clearly as well as end with Type to clearly see it's a TYPE definition. For example, CoordinateType. A variable declared as that type will omit the Type ending. Hence <scope> Coordinate AS CoordinateType. Depending on the situation, an array of a given TYPE definition will be TypeNameList or the plural form of the TypeName. Finally, Global variables declaration will start with Global and the name of the variable. Here's an example of all these:
TYPE WindowType WindowNumber AS INTEGER WindowTitle AS STRING WindowTop AS INTEGER WindowLeft AS INTEGER WindowHeight AS INTEGER WindowWidth AS INTEGER ForeColor AS INTEGER BackColor AS INTEGER END TYPE DIM Window AS WindowType DIM WindowList() AS WindowType ' OR DIM Windows() AS WindowType DIM SHARED GlobalWindows() AS WindowType
- File Names:
ERGONOS DEVELOPMENT STAGES:
And now that we have all the standards defined and out of the way, we can really begin to orient the project towards it's development. As you remember from the 2nd part of this series, I've enumerated 8 seperate engines that make up ErgonOS. If you had the User Interface part to the list (All the controls I enumerated and detailed) there's really 9 main engines. The main thing to remember is that all these 9 engines need to work together so they will need some means of communicating with each other and have access to each other's functionality. However. You might even think at this point (especially if you have a group of 9 people working on the project) that each could take an engine and just start coding. But that's not entirely the case. Some of these engines can be started independantly, others however will need one or more of the engines to be completed prior to their development. Let's Take these engines right here and determine their realization criteria. This way we'll know what should be done first (and even what parts of the engine, if any, could be created independantly of the the others).
- L.A.H.P. (Low level Access to Hardware and Peripheral)
This is a completely independant engine. This is basically a 2 level library that will give the developer (me so far in this case) a standard system of acquiring low level information about the computer ErgonOS is currently running on. If you recall in the 2nd part of the series, the two level are there so that I only need to change the 2nd level to adapt the library to work on any architecture. - V.A.D.M. (Video Access Device Manager)
In a way, this could depend on LAHP to atleast know some basic information about the video card. But the dependance is so small that it can be done more than one way. VADM will basically determine the text and graphics resolution that are available. Store them internally so that ErgonOS can use them to offer the user the ability to change the screen resolution and color depth. - O.S.S.I.S. (Operating System Services Interface System)
Each OS (Windows, Linux, DOS, BSD and others) have a very different way to offer access to the drives and other parts of the system hardware such as RAM, serial and parallel ports and the likes. OSSIS will be an interface between ErgonOS OS calls and the OS functionality. Once LAHP and VADM are done we can begin to use these through the OS itself. Since our first operating system is DOS. We'll be using interrupt calls like 10h for video, 21h for Disk access and management, 33h for mouse etc etc. When we talk about OSSIS, later in the series, I'll detail all the different DOS interrupt there are and how to make good use of them. - A.S.D.A.M. (Architectural System for Digital Audio and Music)
This engine depends on OSSIS to realize itself. So it can know how to activate the sound card if needed (and if possible on the OS). Some sound cards still work in DOS but the recent cards don't. So we'll just have to see what we can do about that (if anything) when we get to ASDAM. - A.R.M.S. (Advanced Resource Management System)
This is, in essence, the memory manager system. ARMS will determine if a certain resource can be created or not (if there is enough memory to do so) and if there is it will create the resource that is needed. When a resource needs to be removed ARMS will take care of that too. It will be designed to allow the creation of any type of resources so it does not need to depend on anything else to get created except for OSSIS (to determine the memory capacity, swap file (if any is needed) and the likes. Then, once it has determined that, it just gets itself ready to receive resource requests and process them in the order they are received. - T.E.M.S. (Tasks and Events Management System)
This is a core engine. By that I mean that ErgonOS just isn't functional without TEMS being implemented. It will be responsible for getting any and all events that are happening in ErgonOS (be it a system event or a user event), determine who created the event (which document, which control, which dialog if a dialog is currenly being shown) is sending the event and then go about processing the event accordingly. If there's any information to carry with a given event, it will be carried throughout the ErgonOS system on a global message queue. TEMS is central and stacked at the same time. THis means that the while environment will use TEMS as well as each individual control or document. So it doesn't really depend on anything as far as development goes but EVRIL will greatly depend on TEMS for obvious reasons as well as the Task Scheduler (which is why I grouped the Tasks and Events together like this). - C.A.P.S. (Command Acquisition and Processing System)
This engine by itself doesn't need anything else. It is basically a string parser that will recognize a given set of commands and perform the action directed by the commands. The best time to realize CAPS is at the same time as EVRIL (below). Since the main design goal of ErgonOS is to allow everything that can be done from the GUI to be doable at the command and keyboard level. It will be a good idea to keep CAPS and EVRIL in sync. - E.V.R.I.L. (ErgonOS Visual Representation and Intelligent Layout)
This is what I decided to call the Visual side of ErgonOS. Atleast the visual drawing, resource creation for all the components can be created. However to code any part of the user interaction side of things we'll need TEMS and ARMS to be in working order before we can get to that part. The main reason is it would be a waste of time to create a temporary mechanism of managing events and memory only to replace them with the functionality of ARMS and TEMS. - E.A.G.L.E. (ErgonOS Application Generation Language and Environment)
This one is independant as well for the most part but only after the rest is done will we be able to really complete EAGLE. We could, at any time define the language entirely, create a lexical analyzer and syntax checker of all the grammar involved at any time but I chose to do it last to keep the language as independant as possible as far as OS and system architecture are concerned, I'll obviously need LAHP, VADM, ASDAM, ARMS, TEMS and EVRIL to be done so I can use their functionality instead of creating a second alternative to the same functionality. The compiler and linker (at the very least) will need to be done at the end but the parser, lexical analyzer and syntax validator can be done anytime I want as they don't rely on any ErgonOS specific engine to be able to do their job.
There you have it. With these relationships established it will be easier to determine what's what and what should get done before whatever else. There's basically one thing left to determine and that is code module organization. That again is also mostly a question of personal taste. But just so you don't get lost in how I'll be doing things, I will explain to you now the way modules will be created and managed. Since these are all based on the engines themselves I will break them down by engine.
ERGONOS CODE MODULE ORGANIZATION:
When it comes to bigger projects, I believe that organization even at the module level, brings many benefits from the start of the development stage all the way down to the debugging phase. If you can create a system that can instantly tell you what code lies in what module instantly, it becomes much more trivial to locate where, in all the thousands of lines of code, something you're looking for is. As such, I try to organize my code in a way that really helps isolate every type of code into it's own module. Since ErgonOS has 9 engines it's easy to see how quickly code could get confusing if it was all in one module only. Code Module Organization along with intelligent error reporting can really make locating a bug a breeze. Of course Error management isn't the most passionate side of development so I like to get rid of it as soon as I can so I can get to the real fun part. So then, for each engine, each related module file will have a corresponding filename to clearly indicate, before we open the file, which engine the file belongs to and what I can expect to find in each of the engine's related modules. Here's the layout of the file naming conventions.
ErgonOS Module Naming Convention | |
Module Name | Description |
<EngineName>Constants.bi <EngineName>Enumerations.bi <EngineName>UserTypes.bi <EngineName>EngineCore.bas <EngineName>Helper.bas <EngineName>Communicate.bi <EngineName>UserInterface.bi <EngineName>Events.bi |
constants go here. ENUM definitions go here User Defined Types go here Core Functionality goes here Functions that help the engine go here Information and code to transfer with other modules User Interface related code goes here Event Management related code goes here |
Now some of these modules may not be needed for a given engine. For example, the low level libraries that do not interact with the user at all won't need the UserInterface and Events module Others might not need to communicate with other modules either, it all depends on the role of the engine in the ErgonOS hierarchy. As an example, let's illustrate this using the EVRIL engine so you can see how things will appear in the folder.
E.V.R.I.L. (ErgonOS Visual Representation and Intelligent Layout) | |
Module Name | Description |
EVRILConstants.bi EVRILEnumerations.bi EVRILUserTypes.bi EVRILEngineCore.bas EVRILHelper.bas EVRILCommunicate.bi EVRILUserInterface.bi EVRILEvents.bi |
constants go here. ENUM definitions go here User Defined Types go here Core Functionality goes here Functions that help the engine go here Information and code to transfer with other modules User Interface related code goes here Event Management related code goes here |
Aside the engine modules themselves there will be one set of module that will serve the purpose of containing elements that are shared among all other engines. For example, Keyboard Constants don't need to be defined at an engine level as any module that needs to access the keyboard can do so using the same constants. Such types of code will go in modules that are prefixed with "Shared". Here are the shared modules that we will need:
ErgonOS Shared Modules | |
Module Name | Description |
SharedConstants.bi SharedEnumerations.bi SharedUserTypes.bi SharedHelper.bas SharedCommunicate.bi SharedUserInterface.bi SharedEvents.bi SharedErrorManagement.bi |
Shared constants go here. Shared ENUM definitions go here Shared User Defined Types go here Shared Helper functions and subs Shared Module inter-communication facility Shared User Interface related code goes here Shared Event Management related code goes here Shared Error Management and Logging facilities |
Finally, as far as the user interface is concerned, each component that I have enumerated in the second part of this series will be implemented independantly in their respective module. Some modules will be bigger than others but by grouping these into control related functionality, it will help keep the whole user interface in perspective. Therefore, here are the control specific modules needed by the user interface:
ErgonOS Component Modules | |
Module Name | Description |
LabelComponent.bas ButtonComponent.bas CheckBoxComponent.bas RadioButtonComponent.bas TextBoxComponent.bas MaskedBoxComponent.bas SelectorBoxComponent.bas PictureBoxComponent.bas ListBoxComponent.bas ListViewComponent.bas TreeViewComponent.bas ScrollBarComponent.bas ComboBoxComponent.bas CalendarDropComponent.bas NumericDropComponent.bas ToolbarComponent.bas ToolboxComponent.bas MenuBarComponent.bas PulldownMenuComponent.bas PopupMenuComponent.bas StatusBarComponent.bas IndicatorBarComponent.bas TabbedDialogComponent.bas TabStripComponent.bas DocumentComponent.bas UserDefinedComponent.bas |
Static Label used to display text on the screen Button that can be clicked, pressed or unpressed Square component that can be checked and a label Round component that only one can be checked Standard text entry field type component Text entry field that can block unwanted characters Text entry that has 2 buttons to increment/decrement Rectangular component to display an image Rectangular list of selectable Item Non Editable Grid viewing with images with multiple views Control to represent data in a hierarchic fashion A Slider Type control that has a min, max and current value Combination TextBox, Button and ListBox components Combination Maskedbox, Button and Calendar components Combination textbox with 2 button components Component that holds other components Floating control that works like a toolbar component Top of form rectangular area to hold menu options Vertical selection appearing below a MenuBar component Same as Pulldown Menu that appears where the mouse is Bottom of screen rectangle that displays information Bottom of screen rectangle to show indicators Tabbed component that can have different page types Tabbed component holding many of the same page type Component to manage all supported document formats Component to create original components from scratch |
Note that this is my way of doing things, and in no means do I intend to "force" you to use this particular naming convention for your own projects. Since there are many modules involved I believe that by naming the modules accordingly and keep code organized in this manner it will greatly help the coding process by allowing me to see what is where even without opening the module. It also makes each module smaller and much more managable. For your projects you can create any standard your want because it's your project and the first person that needs to understand what's going on is you. You might work better with less modules and mode code in each module, you might even break your modules into even more modules than I've done. It all depends on what you're comfortable with and your way of doing things.
Remember that this series is to teach you everything you need to know about making a GUI. As such, and because different people read and understand things differently on different levels, I will be keeping the tutorial series and it's related code modules as clear and documented as possible so that everyone can read and understand the concepts I will be using. This is also one of the reason why the code will be organized the way it will be and why I will be following the standards I've established in this 3rd part of the document. For a project this size, not having some form of organization like this would defeat the purpose of writing this series. Let's continue with another important aspect of development (in GUI or any other programming project), I'm talking about Error Management.
ERGONOS ERROR MANAGEMENT:
Now some of you might be wondering why I'm talking about error management even before any line of code is written. The answer is simple. In big projects, if you don't implement error management right from the start, it will be a major headache to add it later in the project or at the end after you're done coding everything else. This project will easily have several thousand lines of code and atleast several hundred subs and functions, maybe thousands, imagine having to go through each and everyone of these subs and functions and having to add error management code to them all. By adding that code at the beginning it will not only rid you of he big burden of adding it later but also help you greatly in your testing and debugging phase. You'll thank yourself (and me, maybe) in the long run.
Throughout all the modules, when implementing the error management code, I'll want to be sure that it is as easy and quick to locate a bug or an error as possible. For that to successfully occur, I need a certain list of information to help me in my quest. Whether the bug is reported on screen or logged to a file this information will still help locating bugs quickly. Here's that information:
Error Information | Description |
Engine Name Module Name Sub/Function Name Parameter List Error Number Error Description |
The Engine name is displayed The Name of the .bas or .bi file The Sub or Function Name The values of the parameters passed The error number as returned by ERR A human readable description of what the error means |
This essentially means that whenever an error is returned by the system, it will give me back these specific pieces of information. By knowing right from the start, the Engine where the error occured, the module name, the sub or function name, the values of the parameters that were passed to that sub or function (if any) and the error that occured, imagine how fast the debugging process will suddenly become for me, or anyone else correcting the error. Here is an example of a typical function and how I will provide the needed information. Let's assume that this function will be in the EVRIL engine for the sake of the example:
' ============================================= ' NAME: MouseOverControl() ' PARAMETERS: XPos AS INTEGER ' YPos AS INTEGER ' Width AS INTEGER ' Height AS INTEGER ' MouseX AS INTEGER ' MouseY AS INTEGER ' ASSUMES: Parameters are positive values ' RETURNS: True if mouse is on top of the ' control, False if it is not. ' CALLED FROM: EVRIL's main logic loop. ' --------------------------------------------- ' DESCRIPTION: This function will take the ' position and dimensions of a ' control, do the math to get ' the range of coordinates that ' the mouse need to be in to be ' considered to be on top of the ' control and compare the mouse ' coordinates to see if it falls ' within the control's range. If ' it does, True will be returned ' to the calling sub. If it's ' not on top of it, False will ' be returned instead. ' ============================================= FUNCTION MouseOverControl(XPos AS INTEGER, YPos AS INTEGER, _ Height AS INTEGER, Width AS INTEGER, _ MouseX AS INTEGER, MouseY AS INTEGER) AS INTEGER ' ------------------------------------------------ ' We create a variable to hold the error message ' ------------------------------------------------ DIM ErrorMessage AS STRING DIM BottomRightX AS INTEGER DIM BottomRightY AS INTEGER DIM WorkResult AS INTEGER ' -------------------------- ' Turn in Error management ' -------------------------- ON LOCAL ERROR GOTO ErrorManager ' --------------------------------------- ' Evaluate the bottom right coordinates ' --------------------------------------- BottomRightX = XPos + Width - 1 BottomRightX = YPos + Height - 1 ' ------------------------------------------------------------ ' Compare Mouse coordinates to control's area and return the ' proper value of the function based on if the mouse is over ' the control or not. ' ------------------------------------------------------------ IF (MouseX >= XPos AND MouseX <= BottomRightX) AND (MouseY >= YPos AND MouseY <= BottomRightY) THEN WorkResult = True ELSE WorkResult = False END IF ' ------------------------------------------------------------- ' Return the value and exit the function right here before ' execution of the error management section can take place ' if no error occured. ' ------------------------------------------------------------- MouseOverControl = WorkResult EXIT FUNCTION ' ------------------------------------------------------------------- ' This is where the error management message is created and printed ' ------------------------------------------------------------------- ErrorManager: ErrorMessage = "Engine: E.V.R.I.L." + CHR$(13) + _ "Module: EVRILEngineCore.bas" + CHR$(13) + _ "Function: MouseOverControl" + CHR$(13) + _ "Parameters: (" + STR$(XPos) + ", " + _ STR$(YPos) + ", " + _ STR$(Height) + ", " + _ STR$(Width) + ")" + CHR$(13) + _ "Error: " + STR$(Err) + ":" + Err$ ' ----------------------------------------------------- ' The full error message is now created. ErgonOS will ' have an error logging facility that when turned on ' will add this error to a log file along with time ' and date. If not we'll just display the error in ' a standard message box. ' ----------------------------------------------------- IF ErrorLogging = True THEN LogError(ErrorMessage) ELSE DisplayError(ErrorMessage) END IF ' --------------------------------------------------------------- ' In this case, we resume execution loging or showing the error ' --------------------------------------------------------------- RESUME NEXT END FUNCTION
As you can see, it takes a little bit extra coding to implemented error management properly. I'd rather have to add 10 lines everytime I create a sub than to have to find and add a couple 100 or 1000 lines of code by doing this part in the end, after the whole coding process is done. Error management is really there for two purposes. The first is of course to document the error when it occurs in an intelligent way that will help you know where to look and what type of error you'll be fixing. But it's also there so that the program doesn't quit in an uncivilized way. For example, say the user has a document currently opened. When there's no error management and the program just exists, there's a risk that the document might be either lost or corrupted (especially if the error occured as you were saving the file). Proper error management can prevent alot of these situations that make your system that much more stable and reliable to the users.
PART THREE NOW CONCLUDED:
This is it for the third part of the GUI Development Tutorial. The main thing to remember from this series and especially this third part is that you are free to create your own names, standards (if you want), your own component names, your own graphics, your own style. What you are reading here is based on my own project and as such I have the freedom to name things the way I want to. In the same frame of mind, because this is a tutorial series designed to make you, the reader, understands everything there is to know about GUI development from the very start to the end, I am making special efforts to name things very clearly and use a standard that can help you learn the type of coding I will do as well as how you can implement similar routines and structures in your own projects. The name of the game here is that GUI development should most of all be fun to make and to use after you made it. This means if you don't want to implement Error Management right away, you don't have to, but my own personal and professional experience has thought me that if you are planning to implement error management (which I highly recommend on any project that might be used by other people) then right now is the best time to do it because you can then integrate error management in your regular coding routine. As well, when you're done coding, you won't have to go through all your code to insert the error management code later.
I think we've now had enough of the theories, standards and conventions, the next part of this series will now concentrate on the ErgonOS development itself. We'll begin to define the information we'll need when creating components, or whole system of components, we'll start to create coding structures for this information, and we'll begin to put some code in all these modules we defined in this third part of the series. I hope you're enjoying the series so far, I sure am enjoying writing it for you (of course) but for me as well, since it's my GUI project, needless to say that this series will serve as an excellent document for the development of this project. So then, let all this knowledge sink in good. Maybe you've already started to document your own project as you're reading this and you'll want to do some documentary work on your project, go right ahead. For a project like this, you'll only thank yourself in the end. As always, if you have questions, if something in this document (or the other documents in the series) that isn't quite clear or that you'd like me to elaborate on in even more details, send me an email and let me know all about it. Untill Next time, happy reading, and learning.
MystikShadows
Stéphane Richard
srichard@adaworld.com