Elgrint Developer's Guide

Welcome to the Elgrint Developer's Guide. This Guide includes all the essential information, needed to create graphic user interface apps using the Elgrint library. The complete description of the Elgrint interface is available in the Reference, but it is recommended to read this Guide fully before using the Reference.

Your feedback is important to us. After completing this guide, please tell us what you think about it, especially how much time and effort it took to go through it.

Go to the Feedback form after reading this Guide. Note that feedback counts as contribution (see Contribute to Elgrint for details).


Table of contents

Chapter 0. Getting Started

0.1. Requirements

0.2. The basic app

0.3. "Hello World!" app

0.4. Summary:

Chapter 1. Window classes

1.1. Windows basics

1.1.1. Definitions

1.1.2. Parent-child hierarchy

1.1.3. Construction

1.2. Predefined window classes

1.3. Example: Calc 1.0

1.4. Window pointers

1.5. Window plugins

Chapter 2. Non-window classes

2.1. Collections

2.1.1. MVector

2.1.2. MHash

2.1.3. MMap

2.1.4. MWidthList

2.1.5. MList

2.1.6. MSortList

2.1.7. MChar and MString

2.1.8. MColor and MImage

2.1.9. Example: Calc 1.2

2.2. Interface fundamentals

2.2.1. Typedefs and enumerations

2.2.2. Structs

2.2.3. Constants

2.2.4. Global functions and macros

2.3. MApp

2.3.1. Exceptions mechanism

2.3.2. App control functions

2.4. MSys

2.4.1. System configuration

2.4.2. The system clipboard

2.4.3. The file system

2.5. MFile and MTextFile

2.6. Multithreading

2.7. Example: Calc 1.3

Chapter 3. The message system

3.1. The Message Loop

3.1.1. Message

3.1.2. Message queue

3.1.3. Message handler

3.1.4. Message stack

3.1.5. Message propagation

3.1.6. Circular message

3.1.7. Nested loops

3.1.8. Loop owner window

3.2. Example: Calc 1.4

3.3. To recap all that we have learned so far in this chapter:

3.4. Input messages

3.4.1. KeysEntered

3.4.2. StringEntered

3.4.3. CursorMoved

3.4.4. TimerExpired

3.5. Presentation messages

3.5.1. FocusChanged

3.5.2. HotChanged

3.5.3. EnablingChanged

3.5.4. RectChanging

3.5.5. Resized

3.5.6. Moved

3.5.7. ChildListChanged

3.5.8. Paint

3.5.9. Notice

3.6. System messages

3.6.1. RemoteNotice

3.6.2. DirectoryChanging

3.6.3. DirectoryChanged

3.6.4. SystemSuspending

3.6.5. SystemResumed

3.7. Example: Calc 1.5

3.7.1. Message processing in details

3.7.2. The complete calculator

3.7.3. Bonus: improving visual design

Appendix A. Window classes

Appendix B. Messages

Appendix C. Using Elgrint locally with Microsoft Visual Studio

Appendix D. Using Elgrint locally with MinGW 4.8 (command-line, no IDE)


Chapter 0. Getting Started

0.1. Requirements

0.2. The basic app

Open the Cloud Compilation Service in a new window. You will need it throughout this Guide.

Every Elgrint app has to do at least the following 4 things to compile and run successfully:

So, the most basic app may look like this:

Now, copy-paste the above code to the "Type or paste your C++ code here" box of the Cloud Compilation Service, and press the "Compile and download app for MS-Windows". A few moments later your first app should be downloaded to your computer. Run it to see what it can do.

If all is done correctly, you should see your first program running on the screen in just a few seconds. Its exact appearance may depend on your operating system version and the system settings. Under typical Windows XP settings it may look like this:

MMainFrame.jpg

Fun Fact: Of course, the name of the mainframe object doesn't have to be wnd - it can be any legal C++ identifier. In fact, the basic app can be written without any object name, making it even more compact, like so:

Go ahead and compile this code like you did before.

If you play with the basic app, you will notice that the mainframe window is fully interactive - you can move, resize, minimize, maximize, and close it (when you close the mainframe window, the app will terminate automatically).

And all it takes is as little as two short lines of code!

0.3. "Hello World!" app

Before we really get down to business, let's tinker with our basic app a little by adding five new lines to the basic code. Now the code will look like this:

Compile and run this new code. Now the app may look like this:

HelloWorld.jpg

Regardless of your system's settings, you should easily spot five differences between this app and the basic app you created before. Try to figure out which line in the code caused which difference. You should have no problem doing this even if you have never seen Elgrint code before.

Fun Fact: You can create more than one mainframe window within the same app. Try doing it - you should have already guessed how.

0.4. Summary:


Chapter 1. Window classes

1.1. Windows basics

All graphic apps are based on windows. There may be more of them than might seem at first. Let's look again at the basic app you already created. It appears to contain just one window, but in fact there are six: mainframe, central bar (which happens to be of scrollbar type), three buttons, and the screen itself (yes, the screen is a window too):

MainframeParts.jpg

1.1.1. Definitions

Notes:

1.1.2. Parent-child hierarchy

Example of a parent-child hierarchy (the basic app):

WindowsHierarchy.jpg

A child can be floating or embedded. An embedded child is clipped by its parent's boundaries, i.e. only its parts which are inside the parent's rectangle are visible. A floating child is not clipped (except by the screen's boundaries, of course):

Floating.png

To visualize this better, think of the parent as a real house window, and the child as a cardboard poster, and you are standing outside, looking at the house window. Then, a floating child is like a poster in front of the window (outside of the house), and the embedded child is like a poster behind the window (inside the house). You can't see the "embedded" poster if it's obscured by the house wall.

Otherwise, there are no conceptual differences between floating and embedded children.

That being said, the floating children require more system resources, so it is recommended to create as few floating children as possible.

Question: Is the mainframe a floating or an embedded child (of the screen)?

Answer: The mainframe is a floating child, because it's clipped only by the screen (which happens to be its parent as well). Of course, it could have been defined as embedded - it wouldn't have changed anything in this particular case.

Question: Is the screen window a floating or an embedded child?

Answer: The screen is neither floating nor embedded, because it's a parent of itself, and nothing can be behind or in front of itself.

1.1.3. Construction

As we have already seen, the mainframe is created by calling the default constructor of the MMainFrame class. The screen window is created automatically just before the MAppMain function starts - you do not create it.

All other windows are created by calling the init constructor of a suitable class. Elgrint predefines 24 public window classes, including the mainframe and the screen. For every such class, the init constructor has exactly two required parameters and 0 or more optional ones (which are class-specific), i.e. ClassName(parent,rect,...), where:

parent is a reference to a window that will become the new window's parent. The parent cannot be changed after the window is created. Note that MMainFrame class doesn't have this parameter, because its parent is always the screen (no need to specify it).

rect specifies the position and the size of the new window. The position of the window is the location of its top-left corner, relatively to its parent's position. The "rect" is a variable of the MRect type, which is a struct with the following 6 components:

For the MMainFrame class, the rect parameter is the first one and it's optional. We will return to MRect later.

Fun Fact: z and d components are mostly needed for the future support of 3D windows. But since the floating child is located "in front of" its parent, the z coordinate is useful even for flat windows. The d component, however, is always ignored.

Example: Creating 2 child frames, one with z=1 (floating), and one with z=0 (embedded by default)

Compile and run this code. Move the child frames around to visualize the difference between floating and embedded children.

Fun Fact: You may have noticed that most class or function names we've encountered so far start with a letter "M". This "M" stands for "Miranor" - the creator of Elgrint. The "M" helps reduce the possibility of a name collision (among other things).

1.2. Predefined window classes

As already mentioned, Elgrint predefines 24 public window classes. These classes contain enough GUI (Graphic User Interface) for most simple apps, and their functionality can easily be extended and modified to create more advanced apps.

Every window class can be either a container, a selectors, or a label.

The following table provides a general understanding of the predefined window classes. No need to memorize the table - for now it is enough to get the general idea what window classes can do.

Note: Clicking the name of each class in the following table will lead you to the relevant reference section, which contains the complete description and examples, but you don't have to read the reference at this point.

Class Description Essential methods/properties[1]
MWindow
small-MWindow.png
The base class for all the window classes in Elgrint. It can serve as a simple container (an empty rectangle), but its primary purpose is to be used as a base class for creating new window classes "from scratch".
The methods to the right are all the MWindow methods, except those that are related to the message system (we will get to them in Chapter 3).
  • MWindow::MWindow
  • close - closes the window as soon as possible (called automatically when the window object is destroyed)
  • isOpen - returns true if the window is open. The window is open between construction and a call to close
  • getParent - the parent of this window (specified during construction) or itself if the window is closed
  • isAncestorOf - checks if the first window is a parent, grandparent (and so on), of the second window
  • operator== - returns true if the two window objects are actually one and the same
  • operator!= - logical opposite of operator==
  • is<WC> - returns true if the window can be converted to an object of WC class using dynamic_cast
  • as<WC> - converts the window to an object of WC class using dynamic_cast. If conversion fails, returns a harmless dummy object of the suitable type, so the conversion error can usually be ignored
  • getImage - prints a portion of the window as an image (can be then saved to a file or copied to a clipboard)
MProgressLabel
(base: MWindow)
small-MProgressLabel.png
Progress label displays a horizontal graphic progress indicator.
  • MProgressLabel::MProgressLabel
  • Progress (property) - changes the displayed progress (between 0.0 and 1.0)
  • Animated (property) - activates/deactivates animation (false or true)
MInfoLabel
(base: MWindow)
small-MInfoLabel.png
Info label (aka tooltip) is a small window with text, which provides some additional information about its parent window. It is often created when the cursor hovers inside the parent window and closed after the cursor departs the parent window or the user presses any key.
  • MInfoLabel::MInfoLabel
  • Text (property) - changes the text inside the label
  • DistFromCursor (property) - makes the info label follow the cursor (mouse pointer) at the specified distance
MMultimediaLabel
(base: MWindow)
small-MMultimediaLabel.png
Multimedia label can play video/audio files in various formats (AVI, MPEG, WMV, MKV, ASF, MOV, MP3, MP4, WAV, etc.) or record video/audio from camera and microphones into an AVI file. The video (if available) appears in the window itself, and the audio (if available) is played using speakers or headphones.
  • MMultimediaLabel::MMultimediaLabel
  • play - opens a video/audio file for playback and starts playing it
  • record - activates the camera and/or microphone to record video/audio into an AVI file
  • standby - temporarily stop playing or recording
  • resume - cancel the previous pause command, if any
  • off - stop any playback or recording
  • setProgress - play the file from the specified point (meaningless during recording)
  • Volume (property) - change the volume of the audio playback, from 0.0 (silent) to 1.0 (full)
  • getProgress - how many milliseconds have passed from the beginning of playback/recording
  • getDuration - total duration of the playback file in milliseconds (meaningless during recording)
  • getCurrentImage - the currently displayed video frame (can be used for image analysis, for example)
MButton
(base: MWindow)
small-MButton.png
Command button (or simply button) is the simplest of all selectors. It has two possible values, called the pushed and released states. A pushed (clicked) button appears darker and slightly shifted compared to the released (unpushed) button. A button can contain text, or icon, or both. In the latter case the text appears in an info label (MInfoLabel) next to the button.
  • MButton::MButton
  • Text (property) - change the textual content inside the button or in the associated info label
  • Icon (property) - change the graphical image inside the button
  • setPushed - push/release the button programmatically
  • isPushed - returns true if the button is currently pushed
MCheckButton
(base: MButton)
small-MCheckButton.png
Check button (aka checkbox) defines an uncheck/check/undefined state in addition to push/release. Every push/release cycle changes the check state of this button in a closed loop
  • MCheckButton::MCheckButton
  • Tristate (property) - allow the undefined state in addition to the checked and unchecked ones
  • RadioStyle (property) - make the button look like a radio button instead of a checkbox
  • CheckMarkSize (property) - size of the small square/circle in which the checkmark is displayed. If it's set to None, the button has no mark (switch or push-button style)
  • setCheck - set the check state (0: unchecked, 1: checked, else: undefined)
  • getCheck - get the current check state (0: unchecked, 1: checked, 2: undefined)
MDropButton
(base: MButton)
small-MDropButton.png
Drop button is similar to the classic combo-box, except that the values cannot be edited directly (of course, direct editing can be added very easily using MEditBox selector). The drop button is divided into a regular button (main part) and a drop arrow. Clicking the main part is like clicking a regular command button (MButton). Clicking the drop arrow triggers the creation of a drop window via the "DropDown" notice (we will see exactly how notices work in Chapter 3).
  • MDropButton::MDropButton
  • Vertical (property) - align text, icon, and the drop arrow vertically or horizontally
  • dropDown - perform the drop-down operation (called when the user clicks the drop arrow)
  • plugDropWindow - register the window, which is shown when the drop operation is performed
MRadioBox
(base: MWindow)
small-MRadioBox.png
Radio box contains a vertically arranged stack of MCheckButton buttons, but only one button can be checked at any given moment.
  • MRadioBox::MRadioBox
  • init - create all the radio buttons with the specified names (usually done via the constructor, so init is rarely called directly)
  • setValue - check the specified button (the topmost button is 0, next one is 1, etc.)
  • getValue - the currently checked button (the topmost button is 0, next one is 1, etc.)
  • button - a reference to the specified button (often used to disable specific buttons)
MDateBox
(base: MWindow)
small-MDateBox.png
Date box (aka calendar) displays all the days of a given month. A user can select a day of this month by clicking it or by navigating to it with arrow keys, or switch to another month by clicking the special buttons at the top corners or with the PageUp/PageDown keys.
  • MDateBox::MDateBox
  • Range (property) - specify the selectable range of dates
  • setDate - change selection to the specified date (usually activated via user-interface)
  • getDate - the current selection
MSlideBox
(base: MWindow)
small-MSlideBox.png
Slide box (aka slider, scroller, or trackbar) allows the user to select a non-negative numeric value. The slider has one predefined child window (thumb), whose position inside the slider corresponds to the currently selected value. The user can drag the thumb to a desired location within the slider, or to click the edge buttons on the either side of the slider (if any). These edge buttons are not child windows, however - just parts of the slider itself (it's more efficient this way).
  • MSlideBox::MSlideBox
  • MaxValue (property) - determines the selectable range (from 0 to this max value)
  • Vertical (property) - change orientation of the slide box (deduced from initial window size during construction)
  • LineScrollDelta (property) - change selection by how many units when an arrow key is pressed
  • PageScrollRatio (property) - change selection by how many percents of the thumb's length on PageUp/PageDown keys
  • ButtonLength (property) - specify the length of the edge buttons, clicking which is like pressing the arrow keys
  • setValue - change the selection manually (usually activated via user interface)
  • getValue - the current selection (number from 0 to the current max value)
  • thumb - a reference to the thumb child window (often used to change its length, which affects the interface)
MRangeBox
(base: MWindow)
small-MRangeBox.png
Range box (aka ranger, spinner, or spinbox) lets the user select an integral numeric value (positive or negative) by pressing up/down buttons at the side of the box. The range box displays the selected value as a customizable text next to the buttons.
  • MRangeBox::MRangeBox
  • Range (property) - specify the selectable range of numbers (0 to 100 by default)
  • setValue - change the selection manually (usually called via user interface)
  • getValue - the current selection (number within the current range)
  • convValueToString - used internally. Overloading this virtual function allows you to customize the selection display
MSegmentBar
(base: MWindow)
small-MSegmentBar.png
Segment bar (aka toolbar, status bar, or splitter) is divided into parts (segments), arranged vertically or horizontally. Each segment is usually occupied by a child window (of any type). Small gaps between the adjacent children allow the user to change the width of each segment by tracking (dragging) the appropriate gap, unless the tracking is disabled.
  • MSegmentBar::MSegmentBar
  • Vertical (property) - arrange segments vertically (true) or horizontally (false)
  • TrainTrack (property) - if true, dragging one gap drags all the following ones too (like train cars pushed by a locomotive)
  • setSegmentCount - change the number of segments (usually determined during construction)
  • plugSegment - replace a child window in a segment (we will learn more about plugins later on)
  • segment - get the child window inside the specified segment (first segment is 0, then 1, etc.)
MPageBar
(base: MWindow)
small-MPageBar.png
Page bar (aka tab bar) contains multiple child windows (pages), but shows only one of them at a time. The page bar also contains a set of buttons, displayed in a row along the upper edge of the bar. The user can select a page to be displayed, by clicking the corresponding button.
  • MPageBar::MPageBar
  • insertPage - add a page and the corresponding button at the specified index
  • removePage - remove the specified page and the corresponding button
  • setCurrPage - make the specified page the current one
  • plugPage - change the existing page window
  • getCurrIndex - the index of the current page
  • getPrevIndex - the index of the previous current page (useful sometimes)
  • page - reference to a page window (page() returns the current page)
MScrollBar
(base: MWindow)
small-MScrollBar.png
Scroll bar is a container that can contain a lot of content (graphics and child windows), much more than can fit the actual window. The content can be thought to be located on some "virtual scroll" or "canvas", which moves freely within the window (this scroll is similar to a child window, but it's not a real window). The scroll can be moved with navigation keys, or by mouse dragging (if enabled), or using vertical and horizontal sliders (if initialized). A special conjunction button is located between the sliders just to fill the exposed part of the scroll bar, but it can be useful too.
  • MScrollBar::MScrollBar
  • initScroll - create and display the sliders, which control the scrolling (plus some optional configuration)
  • Draggable (property) - allow scrolling by holding the scroll with a "hand" and moving the hand
  • setScrollRect/scrollTo/scrollBy - change the scroll's position and/or size programmatically (usually called internally)
  • getScrollVR - the current size and position of the visible portion (the Visible Rect) of the scroll within the window
  • convScrollToWindow/convWindowToScroll - coordinate conversion (both ways)
MMenuBox
(base: MScrollBar)
small-MMenuBox.png
Menu box (aka menu) displays a vertically-oriented list of items. Each item can contain text, icon, and keyboard shortcut, and can also be checked/unchecked, enabled/disabled, or underlined. An item can be a part of a multi-level command. Highlighting/clicking an item (with navigation keys, or mouse, or by typing a "hot character" of the item) opens a sub-menu (a child of this menu, also of MMenuBox type), which shows the sub-items of the highlighted command. Clicking a leaf item (the final part of the command), selects that command and closes all the opened menus. A command is defined like a directory path, with | character used as a separator, and & marking a hot character. For example, "&File|&Open" or "View|Toolbars|A&dvanced".
  • MMenuBox::MMenuBox
  • ItemHeight (property) - customize the height of items
  • insert - define a new multi-level menu command (e.g. "File|Open") or modify the existing one
  • getCommand - the command string selected by the user (relevant after the menu is closed)
  • getCommandID - the numeric ID of the selected command (sometimes more convenient than a string)
MTreeListBox
(base: MScrollBar)
small-MTreeListBox.png
Tree-list box is similar to a menu, except that all the data is displayed in the same window, and selecting an item doesn't close it. Also, more than one tree branch can be opened (expanded) at the same time. Also, the multi-level items (like menu commands) do not support the hot characters, and the separator can be almost any character, not just |. A child info label is created to reveal partially obscured items, if necessary.
  • MTreeListBox::MTreeListBox
  • ItemHeight (property) - customize the height of items
  • Separator (property) - customize the character, which define multi-level items (| by default)
  • insert - add the specified items as child items to the existing root item
  • setName - change the name of an existing item (e.g. if the user entered a new name)
  • setIcon - change the icon of an existing item
  • reset - remove all sub-items of the specified root item (useful for efficiency)
  • setCurrItem - manually change the selection
  • expand - expand (open) a tree branch (usually called internally)
  • collapse - collapse (close) a tree branch (usually called internally)
  • getCurrItem - the full (multi-level) identifier of the currently selected item
  • getWorkItem - the full (multi-level) identifier of the item that has just been expanded or collapsed
MListBox
(base: MScrollBar)
small-MListBox.png
List box contains a vertically stacked list of simple items (item = text + icon). Unlike menu and tree-list box, where only one item can be selected at a time, the list box allows selecting any number of items (unless this ability is disabled). Selection is done with keyboard and/or mouse in the standard ways. A child info label is created to reveal partially obscured items, if necessary.
  • MListBox::MListBox
  • ItemHeight (property) - customize the height of items
  • MultiSelect (property) - allow multiple items to be selected at the same time
  • insert - insert new item or items (names and icons)
  • setItemName - change the name of the existing item (e.g. if the user entered a new name)
  • setItemIcon - change the icon of the existing item
  • remove - remove an existing item or a range of items
  • select - select an existing item or a range of items
  • deselect - deselect (unselect) an existing item or a range of items
  • setFocusItem - set current/active item (marked by a dotted box). Usually not important, but can be useful sometimes
  • getSelection/isSelected - the currently selected item or items
MEditBox
(base: MScrollBar)
small-MEditBox.png
Edit box (aka editor) lets the user insert any text, by typing it or pasting from the clipboard. Technically, this is a kind of a selector too - the user "selects" a textual value out of an infinite number of possible texts. Also, the user can highlight a portion of the text. The edit box supports all the standard behavior of a non-rich text editor, including undo/redo.
  • MEditBox::MEditBox
  • Multiline (property) - allow line-breaks during editing (true) or write only in one line (false, line-breaks are treated like spaces)
  • ReadOnly (property) - prevent editing the text by a user (the text can still be viewed, navigated, highlighted, and copied though)
  • PasswordMasking (property) - protect the entered text by drawing circles instead of characters
  • insert - insert the specified string into the text at the specified location
  • remove - remove a portion of the text (or the entire text)
  • setText - replace a portion of the text (or the entire text) with the specified string
  • highlight - highlight ("select") a portion of the text (usually performed via user interface)
  • undo/redo - manually perform the undo/redo operations (usually performed via user interface)
  • setCaret - change the location and visibility of the caret (the text editing pointer)
  • getText - extract a portion of the text (or the entire text) as a string
  • getHighlight - the current range of the highlighted text elements
MHtmlBox
(base: MWindow)
small-MHtmlBox.png
HTML box (aka browser) embeds an instance of the Microsoft Internet Explorer into your app as if it was just one of the app's windows. The HTML box is the ultimate selector - it lets the user select and operate websites and web apps, which, theoretically, can be almost anything and do almost anything. This class is one way to connect the app to the outside world (but not the only way), and it can also be used to display some local complex content.
  • MHtmlBox::MHtmlBox
  • setUrl - point the browser to the specified URL (web address), from which to load HTML content
  • setHtml - define the HTML content directly
  • runScript - run the specified JavaScript code
  • isReady - returns true if the HTML content has been loaded (the loading is asynchronous and takes time)
  • getUrl - the URL of the currently loaded HTML content (changes when isReady becomes true)
  • getHtml - the entire currently loaded HTML content
MFrame
(base: MWindow)
small-MFrame.png
Frame (and anything derived from it, of course) is the only container that can be explicitly resized, moved, and closed by the user. The resizing is done by dragging the border, the moving is done by dragging the title area, or by pressing the minimize/maximize buttons on the title, and the closing is done by pressing the x-button (closing button) or Alt+F4. Some of these operations can also be done via the window menu, which is opened by right-clicking the title or by pressing F10. The area below the title is the "bars area", which can contain up to three, vertically arranged child windows: over-bar, central bar (or main bar), and under-bar. The bars define the actual content of the frame, and they can be windows of any type, not necessarily containers.
  • MFrame::MFrame
  • Resizable (property) - turns on/off the user's ability to resize the frame window
  • Caption (property) - change the text inside the frame's title
  • Icon (property) - change the icon at the corner of the frame's title
  • plugBar/plugOverBar/plugUnderBar - replace the corresponding child window (buttons can be replaced too, but that's rarely needed)
  • setRank - manually change the rank state (e.g. minimized, regular, maximized, full screen...)
  • bar/underBar/overBar/xButton/minButton/maxButton - a reference to a child component
MDialogBox
(base: MFrame)
small-MDialogBox.png
Dialog box (or simply "dialog") lets the user select a response out of 1, 2, 3, or 4 given options, represented by a row of buttons. When the selection is made, the dialog box closes automatically. It is also possible to close the dialog without responding (like closing any MFrame).
  • MDialogBox::MDialogBox
  • init - specifies the content of the dialog (usually specified during construction)
  • getResult - user's selection (the index of the selected option/button: 0, 1, 2, 3, or -1 if nothing was selected
MFileDialogBox
(base: MDialogBox)
small-MFileDialogBox.png
File dialog box (or simply "file dialog") lets the user select an existing file or directory. The dialog contains an MTreeListBox window (as its main bar), which displays all the files and directories on the computer. The user can select one file or directory by double-clicking it or by selecting it and pressing "OK", after which the dialog closes. The dialog can be closed without selecting anything (like any other MFrame).
  • MFileDialogBox::MFileDialogBox
  • init - specifies the parameters of the dialog (usually specified during construction)
  • getResult - the name of the selected file, or empty string if nothing was selected (overrides MDialogBox::getResult of the parent class)
MMainFrame
(base: MFrame)
small-MMainFrame.jpg
Mainframe (aka top window) is very similar to MFrame, but, as already mentioned, it is the only window, which can be a child of the screen (even MFrame can't). Every app has to create at least one mainframe, because it is a bridge between the app and the screen. Each mainframe has a corresponding taskbar button and an entry in the Task Switch (Alt+Tab) and the Task Manager (Ctrl+Alt+Delete) windows.
  • MMainFrame::MMainFrame
  • ScreenSaverAllowed (property) - blocks a screensaver (false) as long as this mainframe is active (useful for media players, for example)
  • flash - attract the user's attention to an inactive mainframe by flashing its taskbar button three times
MScreen
(base: MWindow)
small-MScreen.jpg
Screen window serves as the root of the window hierarchy and represents the external environment (the system, input devices, and other apps). It is created automatically when the program starts. The sole instance of this class can be accessed via the MApp::screen function (we will learn more about MApp in Chapter 2). The size of the window is equal to the size of the screen in pixels (screen resolution).
  • StatusIcon (property) - display an icon associated with this app in the bottom corner of the screen
  • getWorkRect - the current working area of the screen (the entire screen minus the taskbar, if any)
  • getWindowAt - a reference to the topmost window at the specified point on the screen

Note: Appendix A contains another table of window classes, with some additional parameters, which we will cover in Chapter 3.

1.3. Example: Calc 1.0

Now let's try actually creating some windows. The following example constructs a prototype for a simple Calculator app. We will spend the remainder of this Guide developing this Calculator app by using all the things we learn along the way.

Calc10.cpp:

Notes: You probably noticed that the buttons and the display box are children of frm.bar() (the central bar child of the mainframe) and not of frm itself (so they are grandchildren of frm). We could just as well put the buttons directly on the frm (try it) - it wouldn't change anything at this point. But putting them on the bar has some advantages, as we shall see later.

Fun Fact: Actually, the constructors of window classes often have more than 2 parameters, but the other ones are always optional and needed only to reduce the number of lines of code. In the above example we "cheated" a little by putting two statements in one line. The optional parameters allow doing this without cheating.

Compile the above code and run the resulting app. You should see something like this on your screen:

Calc10.png

It really looks like a calculator (albeit crude), its buttons respond to clicks and even "light up" if you just move the mouse pointer over them. But the calculator isn't really working yet - pressing the buttons doesn't affect the result on the display. This is because we need to "teach" the app to respond to the buttons being pressed, and we will see how it's done in Chapter 3. But before we get there, we still need to learn a few things.

1.4. Window pointers

So far we have only created window objects "on the stack", simply by defining the objects in the regular C++ style, such as:

However, such a window can only be created once, because a window object cannot be redefined or copied.

But sometimes we want to close and reopen a window at any given time, dynamically, or even to change its class. Also, sometimes we need to copy an object, for example if we create it inside a function and wish to return this object to the caller. In C++ the usual way to accomplish this is by using pointers and dynamic allocation, such as:

As you can see, using the pointers allows us to recreate windows as many times as we want, and even change their type in the process.

HOWEVER, using pointers in general is not a good idea - they are notoriously unsafe and if not handled with proper care can make your app unsafe, or even crash it. With pointers to window objects, there is another problem - if you deallocate a window that is currently processing messages (we will learn about messages in Chapter 3), the program may crash even if the pointer was handled properly.

To solve all these issues, Elgrint defines a special kind of a window pointer - the MPtr class template. It works just like a pointer, but it is completely safe - no dangling or garbage pointers, no NULL dereferencing, no memory leaks, and no problems with the message processing.

Note: MPtr doesn't work with MMainFrame class (and there is no need for it).

Fun Fact: Elgrint avoids the "regular" pointers not just in this case. In fact, the entire Elgrint public interface contains not a single pointer.

Now let's see the window pointer in action. The following example is identical to the previous one, except that MPtr is used instead of a pointer:

Notice that now there is no delete statement - the deallocation is performed automatically by MPtr. Even if you never assign the window pointer, it won't cause any problems, because MPtr points to a closed dummy window by default. A closed window silently ignores all operations on it, so it is perfectly safe to dereference unassigned (nil or null) pointers, as in the following example:

To sum it up, as long as you use MPtr instead of naked pointers, you don't have to worry about any pointer issues.

Exercise:

Apply what we have learned about window pointers to the Calc10.cpp code that you copied to the compilation page (we will need it in the next chapter). Simply replace each MButton with MPtr<MButton> in the code. Wait, that's not enough - this code will not compile. You need to make another small change for each button. Try to guess what it is. Hint: MPtr usage is similar to that of a "normal" pointer.

Exercise: (Bonus) Do the same for the display window - make dsp a window pointer.

Exercise: (Another bonus) Change the caption to "Calc 1.1". You should already know how.

The new version is identical to the last one, but woth a different caption (if you did the second bonus exercise):

Calc11.png

Fun Fact: Instead of MPtr<MButton> we could use MButtonPtr (a so called "window pointer wrapper"). The same is true for other predefined window classes, except MMainFrame (which doesn't work with pointers). An MXXXXPtr type (where XXXX is a window class) is basically a typedef, but its constructor includes the class-specific optional parameters that we mentioned above. This is needed only for convenience, so it's not important right now. You can read about these pointer wrappers in the WCPtr reference, if you want.

1.5. Window plugins

Now that we know about window pointers, we can use another interesting trick - create window plugins. We will use them in the next chapter.

Actually, there is nothing really new about window plugins - those are regular child windows, but they have some special meaning for the parent. We have already seen 4 plugins: three buttons and the central bar - the children of the MMainFrame (actually, the frame has 2 other plugins: overbar and underbar, but these are not created by default). If you resize the mainframe, these children are resized/moved automatically, because the mainframe knows that these particular children must be in certain places (notice that the calculator buttons are not affected by the change in frame's size).

So how do we create a plugin? Just like a regular child, but only using a window pointer (which is why he had to learn the pointers first).

After the child is created we need to plug it in, using a plugXXXX function of the parent (where XXXX is the name of the plugin).

After that the plugin can be accessed via its revealer, which has the name of the plugin, e.g. bar(). The revealer usually returns MWindow reference, regardless of the actual type of the plugin. This reference can be cast to the actual window type using the as function template, if necessary.

Note: Not all window classes support plugins. MFrame has 6 plugins (and so do MMainFrame, MDialogBox, and MFileDialogBox, which are derived from MFrame), MSlideBox and MDropButton have one plugin each, and MSegmentBar and MPageBar have multiple plugins. That's about it.

Plugin example:

And we made a simple editor app in just 10 lines of code!

Notice that the initial rect of edit box is AutoRect. Actually, it can be any value - it doesn't matter in this case.

Question: Why doesn't it matter?

Answer: As soon as the edit box is plugged in, the mainframe adjusts its size and position automatically (that is the whole point of a plugin, after all).

Question: Why must the plugin be a window pointer?

Answer: The parent of the plugin (mainframe in this case) must remember the plugin (using some internal variable). Since a window cannot be copied, the only alternative to a window pointer would be a regular pointer, which is unsafe, as we discussed.

Question: Why the as conversion function is required for setMultiline, but not for setForeColor?

Answer: bar() returns an MWindow reference, not MEditBox. setMultiline is class-specific (declared in MEditBox), while setForeColor is a common function for all window classes (declared in MWindow). Of course, we could use as in the second case as well - it would have been a redundancy, but not an error.

Question: What happens if we use as<MEditBox> on a bar which is not an edit box? Say, if we use it before plugging in the edit box (when the bar is an MScrollBar)?

Answer: Nothing happens. The conversion returns a closed dummy edit box, which safely ignores any operation on it. However, in DEBUG mode, Elgrint reports a warning, so you'd know why nothing happens.

Exercise:

Actually, we could create and plug the edit box in one statement in the above example. Try doing it. Hint: the mainframe stores a copy of the pointer, and the window exists as long as it's linked to at least one pointer, so we don't have to keep a copy of the pointer on our end.


Chapter 2. Non-window classes

Our calculator is still not working, because it doesn't process the necessary messages. But before learning about the message processing, we need to go over the other elements of the Elgrint interface, which are not directly related to windows, but provide the basis for the window interface, so it's important to get familiarized with them first. These elements include collections and their iterators, typedefs, enums, structs, constants, global functions, macros, and service classes (sounds like a lot, but there are fewer of them than you might think).

2.1. Collections

A collection (or a data structure) is an object that contains a set of other objects (elements). All elements of a given collection have the same type and their number is limited only by the available memory. A collection supports three basic operations: insert elements to the collection, reveal the elements currently in the collection, and remove elements from the collection.

Elgrint implements 8 collection types: MVector<T>, MHash<T>, MMap<K,V>, MWidthList<T>, MList<T>, MSortList<T>, MString, and MImage. Each collection is specialized for certain situations and operations, so choosing the right collection type is important for the optimal performance of your app.

Although the collections themselves are different, all support the following basic methods:

Of course, each collection also supports all the automatic C++ class operations: default constructor (creates an empty collection), copy constructor, assignment operator, and destructor.

Fun Fact: All Elgrint collections can be passed or returned by value - the copy constructor and the assignment operator are very efficient and do not depend on the size or the type of the collection.

Fun Fact: You never have to worry about memory management while using Elgrint collections - the memory is allocated and deallocated internally by the collections themselves. Of course, if you explicitly allocate memory for an individual element, then you do have to release it manually afterward.

The three error reporting methods for collections:

Note: Elgrint has one additional way of reporting errors - the exceptions mechanism - but it is never used by collections, because it's not efficient enough. We will learn about exceptions later in this chapter.

Iterators:

Iterator is a special kind of object, which can be used to reveal the content of a collection, one item after another. There are two kinds of iterators:

Note: MSortList, MHash, and MWidthList support only CIter (not Iter), because the elements in these collections cannot be modified without breaking the consistency of the collection. MString does not support iterators at all. The remaining collections support both kinds of iterators.

Fun Fact: If a collection, that has iterators pointing at it, changes (elements are added or removed), the attached iterators are automatically re-validated, if possible, to point to the same elements as before (more on this later).

Note: Important! Use CIter whenever possible. Using Iter can make your program less efficient under certain circumstances. In any case, destroy or reset the iterators the moment they are no longer needed - both iterator kinds can reduce performance as long as they are connected to collections.

Both iterator kinds (CIter and Iter) are used in practically the same way in any collection, and support the following methods:

Of course, the iterators also support all the standard C++ class operations: default constructor (creates an invalid iterator), copy constructor, assignment operator, and the destructor.

Note: copying an iterator is a relatively heavy operation - don't use it too often (copying an iterator is rarely needed anyway). Iterator copying works only if both iterators have exactly the same type and kind.

The following example demonstrates a typical usage of iterators. In most cases, all you need to know about iterators is in this example:

Note: For MVector and MList, the same operations can be performed without iterators, using the index-based get revealer and set mutator:

This index-based access is safer and more convenient than using iterators, but it may be less efficient, especially if the element type is a large class, rather than a simple int as in the above example.

Now let's go over each collection type (the most important ones are MVector, MString, and MImage - these are the ones we will use in the next examples).

2.1.1. MVector

MVector.png

Description:

MVector<T> is the simplest and the most useful collection - an encapsulation of a dynamic array data structure, in which elements (of type T) can be added or removed only at the end. All other collections are based on MVector (directly or indirectly). MVector supports both kinds of iterators (CIter and Iter).

Useful methods:

The remaining methods, not mentioned here, are needed for convenience, extra efficiency, or internal operations, and are not essential.

MVector reference contains the complete description of the class.

2.1.2. MHash

MHash.png

Description:

MHash<T> (hash table) can insert, locate, and remove any item very quickly. The items (of type T) are unique (no two equal elements are allowed) and the order of elements inside the hash is random and can change profoundly on any insert/remove operation. Also, any insert/remove operation invalidates all the iterators pointing to this hash. MHash supports only CIter.

Useful methods (and one global function):

There are no additional methods in this class or in its iterator beyond those already mentioned above.

MHash reference contains the complete description of the class.

2.1.3. MMap

MMap.png

Description:

MMap<K,V> defines a relation from keys of type K to values of type V, so each element is a key-value pair. Each key is unique within the collection, and corresponds to a single value. MMap supports both CIter and Iter, but Iter can change only values, not keys.

Useful methods:

There are no additional methods in this class or its iterators.

MMap reference contains the complete description of the class.

2.1.4. MWidthList

MWidthList.png

Description:

MWidthList<T> is an implementation of a skip-list data structure, which provides reasonably efficient insert/remove/find operations throughout the collection. However, it is complicated and tricky to use. This collection is used directly only to achieve superb performance in some complex algorithms, such as the operation of the MEditBox window class. Chances are you will not need to use it directly. Instead, use either MList or MSortList collections, which safely and conveniently encapsulate MWidthList for some common usages. MWidthList supports only CIter.

If you do wish to use it directly, see the MWidthList reference for the complete description of this class.

2.1.5. MList

MList.png

Description:

MList<T> is very similar to MVector, but it also supports insertion and removal at any location, not just at the end like the vector. This makes the list somewhat less efficient than vector, so if you only need to insert/remove elements at the end and performance is important, then it is better to use MVector. Otherwise, it doesn't matter much, though MList doesn't support certain operations of MVector, most notably the sort function. MList supports both CIter and Iter.

Useful methods:

MList also supports the following methods for compatibility with the MVector class: get(i), append(x), set(i,x), popBack(), setCnt(n,x), CIter::CIter(list,i), and Iter::Iter(list,i). These are equivalent to the corresponding methods in MVector.

There are no additional methods in this class or its iterators.

MList reference contains the complete description of the class.

2.1.6. MSortList

Description:

MSortList<T> contains an ordered sequence of elements (not necessarily unique), which are always sorted in ascending order, based on the operator< for type T. Any insertion or removal preserves the sorting order. MSortList supports only CIter, because changing the value of an element can break the sorting. The sort list is useful for implementing large editable databases, which have to be sorted at all times, like a phone book. For static databases, that need to be sorted infrequently or only once, a more efficient way is to use the sort method of MVector.

Useful methods:

There are no additional methods in this class or its iterator.

MSortList reference contains the complete description of the class.

Fun Fact: You can easily customize the sorting of MSortList (e.g. sort elements in descending order) by overriding operator<. See the reference for details.

2.1.7. MChar and MString

MString.png

Description:

String is a null-terminated sequence of Unicode-16 (16-bit) characters. Each character is described by the MChar class, which is basically just a 16-bit integer code, which identifies the character according to the Unicode charts (plus some interesting, but not often used revealers - see the reference of MChar).

The string can be converted to and from all C++ built-in types, and also to and from vectors of these types. The conversion can be expanded to any other type by overloading operator<< and operator>>, although only the predefined conversions to MString are implicit. MString doesn't support any iterators, because the index-based revealer and mutator (operator[] and setChar) are efficient enough.

Useful methods and global operators:

The string can also be converted from and to all three standard Unicode formats: UTF-8, UTF-16, and UTF-32. The conversion is done via convToUnicodeX methods (where X is 8, 16, or 32) and the global ConvUnicodeToString functions.

The remaining functions are needed for convenience or performance, and are not essential.

MString and MChar references contain the complete description of the classes.

2.1.8. MColor and MImage

MImage.png

Description:

Image is a fixed rectangular matrix of pixels. The x coordinate goes from left to right, and the y coordinate goes from top to bottom, i.e. the top-left pixel has the coordinates of (0,0). MImage supports both CIter and Iter.

Each pixel contains a color value, represented by the MColor class. MColor contains 4 color channels (red, green, blue, and alpha), each of which can have 256 integer values (regular mode) or 65536 floating point values (extended mode). In either case, the values are between 0 (no intensity) and 255 (full intensity). The default mode is regular, and unless you are developing some highly professional image processing app, you don't need the extended mode.

For most purposes, all you need to know about MColor is the following:

The remaining methods of MColor are needed mostly for completeness and some professional purposes like converting to/from the HSL color space (if you don't know what that is, don't worry about it).

Useful methods of MImage:

The remaining methods are needed mostly for completeness and are not essential.

MImage, MPredefinedImage, MColor, and clXXXX references contain the complete description of the classes and related data.

2.1.9. Example: Calc 1.2

Now let's use collections to improve and enhance the Calc 1.1 app you should have created at the end of Chapter 1.

Using the collections reduces the number of code lines (at least in this case), and the programming efforts.

Calc12.cpp:

Compile this code and run the resulting app.

Calc12.png

In this example we use 3 collections: MImage to create a more representative icon for our app, MString to list all the text marks for the buttons (16 characters), and MVector (with elements of type MPtr<MButton>) to contain all the button objects, which are created in the for loop.

Thanks to this loop the 16 buttons are created using only 6 statements, whereas in Calc 1.0 it took us 32 statements. And it's not just the size - the new code requires much less maintenance. For example, if we wanted to change the size of every button to (40,40) instead of (45,45), we would need to change only two numbers now, and not thirty two as in Calc 1.0. We couldn't have done this without pointers, because window objects cannot be copied and thus cannot be inserted into MVector.

Question: Why do we need to store the button pointers anyway? After all we don't use them after filling the vector.

Answer: Recall that if a window object is destroyed, then the window is destroyed too. A window object created via pointer is also destroyed if there are no pointers pointing at it (of course this is true only for MPtr pointers). So we must keep at least one pointer per button stored somewhere to preserve the button objects. These objects are used internally by Elgrint.

Question: Why do we need a string for the text marks?

Answer: It's the most economic way of specifying text marks within the loop. We could also use a character array (char[16]), but MString is completely safe, while an array has the same flaws as a "naked" pointer (in fact, a C++ array is almost the same thing as a pointer).

Question: Why do we need a custom icon for the app?

Answer: Actually, we don't, but it is much more convenient for the user, because the icon appears not just in the mainframe, but also on the taskbar, and the task switch (Alt+Tab) window.

2.2. Interface fundamentals

Now let's get the small stuff out of the way. It requires little learning effort, but we have to get familiarized with these things before going forward.

2.2.1. Typedefs and enumerations

The simplest custom types in C++ are the ones defined with the typedef and enum keywords.

Elgrint defines only 18 typedefs (not counting the MXXXPtr window pointer wrappers) and 15 enums, so there isn't much to learn here. In fact, this is all you need to know about Elgrint typedefs:

The complete description is available in the Basic types reference.

As for the enums, they are used to define possible values for parameters, or the return values of functions, or the types of data members. It would be best to learn them together with the relevant function or data member. We have already seen one such function - the MImage(pi) constructor, which uses the MPredefinedImage enum.

However, it is important to remember a few principles regarding enums in Elgrint, which make learning them even easier:

The Enumerations reference lists al the enums in Elgrint.

2.2.2. Structs

A more complex type in C++ is a "struct", defined with struct or class keywords. A simple struct (as opposed to a complex class, no matter which keyword is used to define it) is just a set of fields (aka tuple), which together define a single collective value. Most structs have an init constructor, which defines initial values for each field. The parameters of the constructor are the same as the fields, and appear in the same order as listed below, although some parameters can be optional.

The structs can be open (with public fields) or closed (with private fields). Open structs are easier to use, because each field can be accessed directly. But sometimes the struct is not universal enough to be open, in which case the fields are kept private in case their definitions would have to be changed in the future. In this case, the struct provides methods to access the fields indirectly.

Elgrint defines only 4 closed structs, 2 of which we already know: MChar and MColor. The third one is MTransform, which is needed only for painting windows (and even then it's not essential), and we will get to that later.

The remaining struct is MDateTime, which specifies date and time values (as its name suggests):

Elgrint also defines 8 open structs, including the already familiar MRect from Chapter 1:

Name Description Fields (types)
MFont Font parameters for drawing text in windows.
  • name (MString) - font name
  • size (double) - font's point size in pixels
  • attr (MTextAttributes) - bold/italic/underlined/etc.
MFileInfo Info about an existing file or directory. Used as a return value for certain functions.
  • name (MString) - name of the file or directory
  • size (MFileSize) - file size in bytes (0 for directory)
  • dtCreated (MDateTime) - date+time of creation
  • dtModified (MDateTime) - date+time of last editing
  • isVisible (bool) - false for hidden or system files/dirs
  • icon (MImage) - image associated with this file type
MDriveInfo Info about a system drive (disk). Used as a return value for certain functions.
  • isActive (bool) - true if drive has data (disk inserted)
  • isFixed (bool) - false if data can be removed
  • id (MString) - manufacturer-defined identifier
  • label (MString) - user-defined identifier
  • fileSystem (MString) - "NTFS", "FAT32", etc.
  • totalSize (MFileSize) - total size in bytes
  • freeSize (MFileSize) - free size in bytes
MLocus Location in 2D table
  • row (MNum) - index of the table row (from 0)
  • col (MNum) - index of the column (from 0)
MSize Size of a 3D rectangle
  • w (double) - width in pixels
  • h (double) - height in pixels
  • d (double) - depth (not used yet)
MPoint Position in 3D space
  • x (double) - offset rightward from origin
  • y (double) - offset downward from the origin
  • z (double) - offset "out of the screen"
MRect Position and size in 3D
  • x, y, z - same as in MPoint
  • w, h, d - same as in MSize
MRange<T> A closed range of generic type T, e.g. MRange<int>(1,20) defines the [1,20] range.
  • first (T) - lower limit of the range (inclusive)
  • last (T) - upper limit of the range (inclusive)

Notes:

2.2.3. Constants

All the constants in Elgrint are listed in the Constants reference section. They are grouped into 10 sets.

The remaining 6 sets describe the so called "special values": NoneXXXX, AutoXXXX, SameXXXX, MinXXXX, MaxXXXX, and ZeroXXXX, where XXXX is a name of a relevant class without the leading "M", e.g. NoneColor, AutoChar, MinDateTime. The relevant classes are: MLocus, MSize, MPoint, MRect, MColor, MDateTime, MChar, MString, and MImage. In addition to these classes, the following special values are defined for integers: None, Auto, Same, Zero, MaxNum, MaxFileSize, MaxNoticeID, MaxTimerID, and MaxTimerDelay.

Fun Fact: MinColor, MaxColor, MaxString, ZeroDateTime, and all combinations involving MImage except for ZeroImage are not defined. But you don't have to remember that, because these combinations are obviously meaningless.

The first three sets (NoneXXXX, AutoXXXX, and SameXXXX) describe the so called invalid values, which have special meaning for many functions throughout Elgrint. The remaining ones (ZeroXXXX, MaxXXXX, and MinXXXX) describe the proper range limits for various types. Range is not connected to validity - both valid and invalid values can exist inside and outside of the proper range. The actual values of all these constants are listed in the reference, but they are usually not important. The invalid values are reasonably far from the regularly used values to avoid a collision.

Note: For objects of every relevant class, the global IsValid function returns true, if and only if the object's value is valid. Note that invalid values are not necessarily limited to the above constants. IsValid contains the complete definition of validity for each type, but most of the time you don't have to be concerned about invalid values.

2.2.4. Global functions and macros

We're almost done with the small stuff - this is the last of it.

Elgrint defines 13 global functions (not counting those that are directly associated with specific classes) and 25 macros. The full description appears in the Global functions and Macros reference sections, but only a few of them deserve any specific attention. Most of these functions and macros are either needed for internal use or some exotic configuration, which you will probably never use, at least not in the beginning.

The only essential global function is MAppMain, but you already know everything there is to know about it. Other interesting functions are GetKBName, HashNumerator, and GenException, all of which are described elsewhere in this guide.

As for the macros, the only interesting (albeit still optional) ones are the 18 ON_XXXX macros, which we will learn in Chapter 3.

Now that we have dealt with the small stuff, we are left with only one part before we get to the interesting stuff - the four service classes: MApp, MSys, MFile, and MTextFile. To be fare, the service classes are somewhat interesting too - they contain system functions, which allow you to access the system clock, know how much space is left on the hard disk, copy text and images to/from the clipboard, and much more.

2.3. MApp

A set of 14 static functions, which provide app-wide functionality, i.e. related to the app in general, and not to any specific window (recall that window classes take care of individual windows).

2.3.1. Exceptions mechanism

The exceptions mechanism is the preferred way of reporting runtime errors. The other way is the assertions, which we talked about while learning about collections. The problem with assertions is that they mostly exist only during debugging (for performance reasons), and that they terminate the program on error. Exceptions are somewhat less efficient, but they solve these problems.

Using the exceptions is very easy - you simply call the global GenException function, specifying the code of the exception and optional string parameters. The function extracts the title and message strings for the specified exception and calls the global HandleException function, which prints the message on the screen using the MApp::messageBox function.

For all this to work properly, the exception message has to be defined before calling GenException, using MApp::defineException (manual definition) or MApp::loadExceptions (definition via text file). However, all the standard exceptions are defined automatically when the program starts. You need to define/redefine them only if you're adding custom exceptions or customizing the existing messages (e.g. translating them to a different language). Otherwise, GenException is enough.

If you wish to know whether an exception occurred in a given code portion, call MApp::resetLastException before the code portion and after it call MApp::getLastException, which returns 0 if and only if no exception occurred since MApp::resetLastException was called. However, such usage is rarely needed. In most cases, either there is no need to know that the exception occurred (HandleException takes care of everything) or it is more convenient to deduce that the exception occurred by other means. For example, MFile::isOpen returns false on any file error (as we will see soon), and MWindow::isOpen returns false if the window creation failed.

Another way of customizing the exceptions is to replace the HandleException function. For this you need to include "elgrint.h" instead of "elgrint.cpp". Such customization isn't done often though. See MApp::genException for more info.

Note: Important! The "elgrint.cpp" file includes "elgrint.h" and implements the default HandleException. If you don't want to to implement your own HandleException simply include "elgrint.cpp" instead of "elgrint.h" in your main file (the usual case). However, you can only include it once, so include "elgrint.h" in all other files (or the linking will fail). If you do want to implement your own HandleException, include only "elgrint.h".

2.3.2. App control functions

In addition to the functions used by the exceptions mechanism we saw above, MApp supports the following operations:

These are all the functions of the MApp class.

See the MApp reference for the complete description of all the functions.

2.4. MSys

MSys is another set of static functions (similar to MApp), except that here the functions are system-related, not app-related. The distinction is small, but it is needed to avoid a single large class.

For better clarity (and to help you remember them), the MSys functions can be divided into 3 groups, as follows.

2.4.1. System configuration

The remaining functions can be ignored - they are usually needed for some rare and specific purposes. The full list of functions can be found in the System configuration reference.

2.4.2. The system clipboard

A clipboard is a special area in memory, which is shared between all apps, so they can pass data between them. The clipboard can contain a single piece of data, which can be a string, an image, or a list of pathnames (filenames).

The full description can be found in the Clipboard operations reference.

2.4.3. The file system

Elgrint is designed to work across different platforms and different file systems. To achieve this goal, it defines a certain set of rules for pathnames (pathname = path+filename), as follows:

The complete description is available in Pathname reference, but these are all the important points.

All the file system methods of MSys:

See MSys reference for the complete description of all functions.

2.5. MFile and MTextFile

As you may have noticed, MSys class is missing something important. It has functions for finding, copying, renaming, or deleting files, but not for creating or editing them. These operations are handled by the separate MFile class, which is more efficient and powerful than a set of MSys functions could have been, which is why it's a separate class. MFile also supports FTP files, and HTTP data streams (web pages), which may or may not be actual files. MTextFile is a specialization of MFile, which provides additional support for standard text files.

Useful methods of MFile:

Example: Copying a file in just 8 short lines of code.

Believe it or not, this code already includes all the necessary error checking and reporting, thanks to the exceptions mechanism described above. If an error occurs during opening or reading, fromF is closed, isEOF returns true, and the loop ends. If writing fails, then toF is closed and all subsequent operations on it fail silently until EOF is reached in fromF. On success, both files are closed automatically as soon as the function returns (via MFile destructor).

Fun Fact: The MSys class actually contains a copyFile function, but it works only on local files (at least for now). Our 8-line function works with FTP and HTTP files too, so it actually combines 3 functions: copy, download, and upload. If you think about it, those are really one and the same operation. Also, our function can be used to track the copying progress and abort the copying if the user asks for it. MSys::copyFile can't do any of those things, but being a built-in system function it can do one thing we can't do on our own - preserve the file attributes of fromName (especially the "last modified time") and copy them to toName. So both methods are important and complement each other.

MTextFile works on the similar principle as MFile, except that it reads and writes text and not binary data (MFile can read and write strings too but not as a plain text). Thanks to this specialization, the interface of MTextFile is much simpler than that of MFile, because many file operations become meaningless. In fact, most of the time, you only need to use MTextFile as follows:

In both cases, the file is closed automatically right after the operation.

MTextFile supports all standard Unicode formats, as well as ASCII (see MTextFormat). Usually it detects the proper format automatically (getFormat returns the result of this auto-detection). Sometimes, however, the format needs to be specified explicitly, using the optional parameter of open (or the constructor).

Example: Copying a text file in just 3 lines of code (assuming that the file is small enough to fit in memory), preserving the text format

Working with files isn't really necessary when handling windows, but files are important for creating interesting apps: you can use the files to store app data between sessions, to work with the Internet (without the heavy and messy MHtmlBox), and much more.

The MFile and MTextFile references provide the complete description of both classes.

2.6. Multithreading

We are done with all the service classes, but there is still just one more important detail - multithreading.

As you probably know, many modern apps work with more than one thread of execution to improve the user experience. For example, if an app is performing a heavy operation, which takes several seconds (or minutes), it will not respond to user's input until that operation is complete, which is a bad design. The best solution for this problem is to run the operation in a different thread (the so called "worker thread").

Elgrint does not support multithreading programming directly, because the new C++ standard of 2011 (C++11) includes native support for multithreading, so this functionality is now completely portable, and does not have to be encapsulated in a toolkit such as Elgrint. That being said, for compilers that do not yet support the new standard, Elgrint emulates the essential multithreading functionality of C++11:

This emulation needs to be activated explicitly by defining EMULATE_CPP11_THREAD_LIBRARY macro in the compiler settings or right inside the code (see Example below).

Note: The MApp::sleep(d) function is also thread-related - it freezes the thread from which it's called for d milliseconds. But this function is always defined - it doesn't need the EMULATE_CPP11_THREAD_LIBRARY directive.

Not everything can be done in worker threads. The complete information is available in the Multithreading reference section, but the general rule is this:

Anything that deals directly with windows (creating and handling the window objects in any way) can be done only in the main thread (the one that calls MAppMain). Everything else, including MSys and MApp functions (except those that return a reference to a window object) can be used in any thread, subject to the usual data access rules.

Thus, worker threads can be used for background processing, while the main thread displays the results and interacts with the user. Other threads can communicate with the main thread using global variables or the RemoteNotice message, which we will learn in Chapter 3.

Example: Copy a file in a worker thread to prevent the main thread from freezing

You can read more about multithreading in general, and about std::thread and std::mutex in particular in the CPP-Reference. Note that Elgrint emulates these modules only partially, but accurately, so if you switch to a compiler that supports multithreading, simply remove the EMULATE_CPP11_THREAD_LIBRARY definition.

2.7. Example: Calc 1.3

Before we proceed to the most interesting part that we promised, let's refresh our Calc 1.2 app by defining a new window class, CalcFrame, derived from MMainFrame, and moving the initialization code into its constructor. We will need this new class in the following sections.

Now the app will look like this:

Calc13.cpp:

This new code looks different, but actually, most of it is the same, it's just been shuffled around a little (all the configuration of the bar and its children has been moved to the constructor of the new CalcBar class). The new code is a bit larger because defining a new class requires some overhead, but it's a "necessary evil", as we will see. Note that CalcBar defines a standard-form constructor (with 2 parameters). Recall that this is required by the MPtr template (without it MPtr<CalcBar> will not compile).

Note also that the parent of the bar's children is now *this instead of frm.bar(), because they are now created inside the bar (this object). The new bar is then plugged into the mainframe (we learned about plugins at the end of Chapter 1).

Also, four new variables were added (as CalcBar data members). We will use them in future versions (they are not used here). Note that we didn't need to initialize operand1, operand2, and op data members - MString and MChar take care of that automatically.

Question: Why must m_buttons and m_dsp be data members now? Why can't they be declared in the body of the constructor?

Answer: If we define them in the constructor, then they will be destroyed once the constructor returns, along with their windows. This wasn't a problem in MAppMain thanks to runMessageLoop, which prevented MAppMain from returning until the mainframe is closed.

The end result is exactly the same as in the previous version, except for the caption, of course:

Calc13.png


Chapter 3. The message system

Believe it or not, by now we have seen every class and type in the Elgrint interface. We know (at least in general) how to use window classes to construct the framework for an app and configure its appearance.

Now we can finally get to the interesting part - teaching the windows to respond to events, and thus making our apps truly interactive.

3.1. The Message Loop

As we already know, the function that "brings windows to life" is runMessageLoop. So, first, let's see how this function actually works. The following description may seem difficult and confusing at first, but don't worry - it should all become clearer once we get to examples. Just go over the next section and understand the general idea.

General description:

The runMessageLoop function contains a while loop, which extracts a message from a message queue and calls a proper message handler in each iteration of the loop. Messages can be stacked on top of each other during each iteration, unless a circular message is encountered. Message loops can be nested inside each other. Each message loop terminates when its owner window is closed.

As already mentioned, the above description will probably seem confusing at first. This is because it contains seven undefined terms (in bold). So let's go over them, one by one.

3.1.1. Message

3.1.2. Message queue

The queue is a special internal structure, which contains various messages, delivered to the app, but not processed yet. These messages are queued (wait in the queue) until the app can process them, one message per loop iteration (see below). Not all messages are queued - some are stacked (we'll get to the message stack in a moment). If the queue is empty, then the app is suspended until a new message arrives in the queue.

3.1.3. Message handler

The whole point of a having messages is to handle them - that is how the app responds to messages in a customizable way. To achieve this, MWindow class (and therefore all window classes) has 18 virtual functions called "message handlers". For each message named XXXX, the handler is declared as void OnXXXX(); (no parameters and no return value to make it as simple as possible). The ON_XXXX macro can be used to declare a handler of message XXXX instead - it's even more convenient and reliable. runMessageLoop calls an appropriate handler when necessary (more on this below). A handler can call the same handler of the base class, but other than that the handlers shouldn't be called directly.

3.1.4. Message stack

Messages don't have to be queued - they can be processed immediately, if they are generated by the app's main thread (there is no concurrency conflict in such a case). This happens if another message is generated by a message handler. Let's take the following handler implementation as a simple example:

This handler is called by runMessageLoop if a user moves the cursor (mouse pointer) over the window (more on this below). But the setFocus function generates a FocusChanged message, which results in OnFocusChanged handler being called (by the message loop framework) before setFocus returns. So, OnFocusChanged is executed while OnCursorMoved is still running (or, actually, waiting for OnFocusChanged to return). In other words, the FocusChanged message is said to be stacked on top of the CursorMoved message. Any number of messages can be stacked on top of each other. The messages in the stack are not necessarily different, and not necessarily belong to (i.e. handled by) the same window. For example, OnFocusChanged can also call setFocus of another window, which may result in another FocusChanged message stacked on top of the previous two.

Only the topmost message in the stack is active - the rest are "frozen" until all the messages above them are handled and their handlers return. The digMessageCode function returns the MMessageCode constant (mcXXXX) of the current topmost message, although this information is rarely needed.

Once all the messages are handled, the stack becomes empty and the control returns to the message loop, which performs various cleanups and window adjustments (you rarely need to know these details) and then starts the next loop iteration.

3.1.5. Message propagation

Any message can be propagated (redirected) to any other window (let's call this other window destWnd) by calling propagateMsgTo(destWnd) during the handling of the message. The specified destination window receives the same message as the one being currently handled by this window. Propagation is always stacked, i.e. the propagated message is handled before propagateMsgTo returns. This function is called by the default handlers of the KeysEntered and Notice messages, which propagate them to a parent window (see below). Any message can be propagated multiple times (which is often the case).

During the handling of any message, digOrigin returns a reference to the window that received the original message, before it was propagated (if the message is not propagated, the function returns *this). Knowing the origin window of a message is often important when deciding how to handle the message.

propagateMsgTo has one problem - you have to overload the source window's class to call the function from an overloaded handler. This is not always practical, or even possible. The problem is solved with the requestPropagation function. It needs to be called only once (usually right after destWnd is created). After destWnd.requestPropagation(mc,ps) is called, every time a window receives an original (not propagated) message mc, it is propagated automatically, i.e. sourceWnd.propagateMsgTo(destWnd) is called internally right after the message is handled, if at least one of the following conditions holds:

The scheduled propagation can be cancelled at any time using cancelPropagation with the same parameters.

See propagateMsgTo, requestPropagation, and cancelPropagation references for additional (mostly non-essential) info.

3.1.6. Circular message

There is one potential problem with the message stack - it can become infinite, if a message is stacked on top of an identical message for the same window. This can happen if a message is propagated in circles (i.e. passed to a window, which has already propagated this message, so it will probably propagate it again, ad infinitum), but there are other potential ways of creating a circular message as well, such as passing the input focus in a circle.

If this happens, the app (and possibly the entire system) becomes unresponsive for a few moments and then crashes due to stack overflow.

But don't worry - none of this should ever happen, because Elgrint automatically detects such circular messages and blocks them. You only need to know this in case you are expecting a window to receive a message, but it does not. If that happens, check whether you accidentally created a circular message. All this doesn't mean that circular messages are bad. In fact, it is often more convenient to allow a circular message and rely on the automatic blocker, rather than trying to prevent it - this can make the app development process a lot easier.

A message is considered circular, if two equal messages exist in the same stack. Two messages are equal if they are delivered to the same window (as returned by *this), have the same code (as returned by digMessageCode) and the same origin (as returned by digOrigin). The only exception is the Notice message, for which the name and ID (as returned by digNN and digNID respectively) have to be equal as well. If a message is received while an equal message exists in the stack, the new message is completely ignored, unless it was generated by a different loop than the first message, which can happen due to the so called "nested loops".

3.1.7. Nested loops

The runMessageLoop function can be called many times during the program's run, which means that it can be called while another instance of it is already running. This is useful for creating the so called modal windows, which are opened and closed within the scope of a single function (modal windows are not really necessary, but they can be very convenient under certain circumstances).

Loop nesting is somewhat similar to message stacking - only the topmost loop is running and the other loops are "frozen" until the topmost runMessageLoop returns. There is no limit to the number of nested loops, but there is usually no need for more than two. Note, however, that all message loops share the same message queue and all the windows respond to messages in the same manner, no matter which loop is currently running.

3.1.8. Loop owner window

The window that called runMessageLoop has a special status among other windows - it becomes the owner of the loop. The loop runs for as long as its owner is open. Once the owner window is closed, the loop terminates automatically. Any window can be a loop owner, including the screen window, in which case the loop terminates when all the app windows are closed. A window can own more than one loop, i.e. it can call runMessageLoop many times.

See Messages and runMessageLoop reference sections for a more detailed description of all these concepts. Also, Appendix B summarizes the essential information about all the messages.

3.2. Example: Calc 1.4

Now that we know the basics and the terminology, let's look again at that confusing general description in section 3.1:

"The runMessageLoop function contains a while loop, which extracts a message from a message queue and calls a proper message handler in each iteration of the loop. In each iteration messages can be stacked on top of each other, unless a circular message is encountered. Message loops can be nested inside each other. Each message loop terminates when its owner window is closed."

This description should appear a lot more meaningful this time. If you are still confused, don't worry - it takes some time to get used to all these new terms, and you don't have to understand all the details in order to start using the messages - it's enough to have a general idea about the message processing.

The general idea is as follows: to make a window process a message, you need to create a new window class (derived from a suitable window class) and redefine the suitable OnXXXX handler. For example, if you want to customize the Notice message of MMainFrame class, you need to create a class which is derived from MMainFrame and define the void OnNotice() function (or better yet use the ON_Notice macro).

Let's implement this example right now - add the OnNotice handler to our Calc app. The only changes from the previous version "Calc 1.3" (except for the caption, as usual) are the call to setNoticeID while creating the buttons, and the ON_Notice handler in the end of CalcBar definition.

Calc14.cpp

Calc14.png

Now you should understand why we needed the new CalcBar class in the first place - to overload the message handlers such as OnNotice (overloading always requires creating a new class in C++).

The app initially looks the same (except for the new caption), but now if you press a calculator button, its text will appear in the results display, so the calculator responds to buttons now! And all it takes is just a few extra lines of code. In fact, all we need is 2 lines: call setNoticeID to activate the "Released" notice, and call setText from inside the ON_Notice handler.

By the end of this chapter we will find out exactly how this works and be able to complete the calculator, but for now let's go over the messages themselves in greater detail to get a clearer picture of what the messages are and what they do. Before we begin, however, a few clarification notes:

3.3. To recap all that we have learned so far in this chapter:

And now we can finally proceed to the messages themselves.

3.4. Input messages

Four messages, generated by input device events (directly or indirectly). All input messages are queued (wait in the queue until the app is ready to handle them).

3.4.1. KeysEntered

Generated when a key is pushed or unpushed (released). A key, in this context, is not only a keyboard/keypad key, but also a mouse button, a turn of a mouse wheel, a tap on a touch-screen/touchpad, and certain touch gestures (pinch, swipe, etc). Note that keyboard/keypad keys (unlike other keys) generate repeated events if they remain pushed long enough (see MSys::getFirstRepeatDelay and MSys::getRepeatDelay for details).

Delivered to the focus window, except for the mouse wheel events, which are delivered to the hot window. The focus and hot windows are defined in FocusChanged and HotChanged messages below.

Revealer digKeys returns the numeric identifier of the key that caused the event. The result is one of the kbXXXX constants, or their combination (bundle). The only meaningful combinations are of a single regular key with one or more modifier keys: kbCtrl, kbAlt, kbShift, kbSystem, and kbUnpush (the last one means that the specified key was unpushed). The combination can be defined using operator+ or operator| (usually doesn't matter which), for example: kbAlt+kbF4, kbCtrl+kbShift+kbInsert, or kbUnpush+kbEnter.

Default handler (from MWindow base class) supports the tab-stop switching (Tab, Shift+Tab, and Esc keys), and propagates everything else to the parent window. See appendTabStop for a full description of the tab-stop switching.

See full description and example in KeysEntered reference.

3.4.2. StringEntered

Generated automatically right after the KeysEntered message, if the pushed key can be converted to characters (usually just one character). All alpha-numeric keys, as well as kbEnter and kbEsc are converted. Under MS-Windows, kbEnter is converted to a pair of characters ("\r\n"). The conversion procedure accounts for the current input language, Num-Lock and Caps-Lock states, and more.

Fun Fact: In general, this message might be generated by other means of textual input, such as optical scanner or voice recognition system (not supported yet, though).

Delivered to the focus window. See FocusChanged below for the definition of a focus window.

Revealer digStringRef returns the textual input (mostly just one character), which generated this message. The function returns a non-constant reference to the string, so you can change it to any other string (even an empty string) to customize the message processing (see example in the reference).

Default handler propagates the message to the parent window.

See full description and example in StringEntered reference.

3.4.3. CursorMoved

Generated when a cursor (a mouse pointer or a touchpoint) moves relatively to the window (even a little), or moves from one window to another. This can happen not only as a result of an input event, but also if the windows themselves are moved, resized, opened, or closed, thereby changing the cursor's coordinates.

Note: Elgrint interface supports unlimited number of cursors, although in most cases only cursor 0 is used.

Delivered to the hot window. See HotChanged below for the definition of a hot window.

Revealer getCursorPos returns the position of the specified cursor (cursor 0 by default) at the moment when the event was generated (not the real-time position). CursorImage property determines the shape of the cursor (in a form of MImage), although if the screen's cursor is not empty, it overrides the CursorImage property in all other windows.

See full description and example in CursorMoved reference.

3.4.4. TimerExpired

Generated when a previously created timer expires. A timer is created by the setTimer function, which specifies the timer's ID (a number from 0 to MaxTimerID) and the delay in milliseconds until the expiration. Each timer generates only one event. If you want to generate repeated events, call setTimer again in the OnTimerExpired handler.

Delivered to the same window that called setTimer.

Revealers digTimerID and getTimerDelay return the ID and the delay, specified by the call to setTimer, which caused the event. However, getTimerDelay is rarely needed - it is provided mostly for completeness.

See full description and example in TimerExpired reference.

3.5. Presentation messages

Nine messages, generated mostly by the app itself. Indicate a change in state, parameter, or appearance of a window. Presentation messages can be queued, stacked, or both, as described below.

3.5.1. FocusChanged

Exactly one of the windows in the system is defined as a focus window (if the screen window is the focus, it means that the focus is outside of the app - the app is "inactive"). A focus is a special internal state of a window, which is needed to define the origin of the input messages. All the KeysEntered and StringEntered messages are originally delivered to the focus window, except for the mouse wheel events (which go to the hot window first). The MApp::focus function returns a reference to the current focus window, which can change for a variety of reasons (see next).

Generated by the setFocus function, which moves the focus to the window that called this function. Also, if the user clicks a non-focused window, then that window receives the focus. The focus can also change as a result of a system command, such as the task switch, which happens when the user presses Alt+Tab or clicks a taskbar button. Finally, if a focused window is closed or disabled (see EnablingChanged below), then the focus is automatically passed to the nearest enabled ancestor. Messages generated by the system are queued, and messages generated by setFocus are stacked (i.e. the focus changes and all the messages are handled before setFocus returns).

Delivered to the window that gained focus and to the window that lost the focus (in no particular order) every time the focus window changes (right after the change). However, if a window that received the focus calls setFocus of another window in response (in OnFocusChanged), then the focus window is changed again, but the window that "refused" the focus in such a way does not receive another FocusChanged message, because that would create a circular message (receiving FocusChanged while handling another FocusChanged).

Revealer isFocused determines whether the window received the message because it gained focus (isFocused returns true) or lost it (returns false). However, the handling is often similar in both cases, so isFocused is not always required. Note that "MApp::focus() == *this" is equivalent to isFocused(), but it is recommended to use isFocused whenever possible (it's more convenient anyway).

Default handler passes the focus to the parent window (i.e. calls getParent().setFocus()).

See full description and example in FocusChanged reference.

3.5.2. HotChanged

Exactly one of the windows in the system is defined as a hot window (it can be the screen window as well). Normally, a window is hot, if and only if it contains the primary cursor (e.g. mouse pointer), so if the cursor moves from one window to another, the hot state of both windows changes. However, if the window is hot, and its HotCapture property is set to true (using setHotCapture), and the user presses the primary mouse button (usually the left one), then the window remains hot even if the cursor moves outside of the window, until the user releases the button, or the HotCapture property becomes false, or the focus is switched to another app (see FocusChanged above). The "hot capturing" is needed to improve the user experience in certain cases, but is not essential.

Generated internally based on the cursor events, window locations, mouse buttons, and HotCapture properties. Always queued.

Delivered to the window that gained the hot state and to the window that lost the hot state (in no particular order) every time the hot state changes.

Revealer isHot determines whether the window received the message because it gained the hot state (isHot returns true) or lost it (returns false). However, the handling is often similar in both cases, so isHot is often not required. Note that "MApp::hot() == *this" is equivalent to isHot(), but it is recommended to use isHot whenever possible (it's more convenient anyway).

See full description and example in HotChanged reference.

3.5.3. EnablingChanged

Each window can be enabled or disabled, except for the screen, which is always enabled. A disabled window cannot be focused and does not receive KeysEntered and StringEntered messages (even propagated ones), although it can be hot and can receive CursorMoved messages. All app windows are implicitly enabled by default.

Generated only by enable and disable functions (never by the system). This message is never queued, i.e. it's handled before the enable/disable functions return.

Delivered to the window whose enabling state has just changed. By default, if a window is enabled/disabled, then all of its descendants are enabled/disabled (respectively) as well, and every one of them receives the EnablingChanged message. This is because, as already mentioned, every window is initially in an "implicit" (aka "by parent") state, i.e. its enabling state is equal to that of the parent. However, a window on which the enable/disable function is used directly becomes explicitly enabled/disabled, and no longer depends on the parent's state. That being said, using enable on an explicitly disabled window returns it to the implicit "by parent" state (thus, calling "disable(); enable();" always makes the window's enabling state implicit, equal to that of the parent).

Revealer getEnabling returns the current enabling state (MEnablingState enum) of the window: esEnabled, esDisabled, or esByParent. The isEnabled function returns true iff the window is actually enabled (that depends on the ancestors' states if getEnabling returns esByParent).

Default handler repaints the entire window.

See full description and example in EnablingChanged reference.

3.5.4. RectChanging

Generated just before size and/or position of a window changes as a result of setRect function or one of its derivatives (setSize, setPos, bringForward, or sendBackward, although the last two don't generate messages at this time). If the window rectangle changes due to a system command (e.g. tile/cascade windows), this message is not generated. But when it is generated, it's always stacked (handled before setRect returns).

Delivered to the window that called setRect or its derivative.

Revealer digRectRef returns an editable reference to the intended rectangle, i.e. the size and position a window is going to have if this message is ignored (ignoring it is the default behavior). OnRectChanging can be used to change the fields of the MRect reference returned by digRectRef to affect the outcome of resizing/moving before it happens (in fact, that's the whole point of having this message). For example, it can be used to enforce minimum/maximum size, limit the movements of the window on the screen, or support the auto-sizing and/or auto-positioning, by replacing Auto values in MRect with valid values. Setting a field to Same cancels any change to that component (in particular, "digRectRef() = SameRect" cancels the setRect operation altogether).

getRect and its derivatives (getSize and getPos) return the current size and/or position, because those haven't changed yet (recall that RectChanging is a pre-note). This information can also be useful.

If after this message the window size and/or position actually changes, the window receives Resized and/or Moved messages, which are described next.

See full description and example in RectChanging and setRect references.

3.5.5. Resized

Generated after the size of a window changes as a result of a setRect function or its derivative setSize, or as a result of a system command (system commands can affect only mainframes and the screen), or internally by Elgrint. If the size changes due to a system command, the message is queued, otherwise it is stacked (handled before setRect returns).

Delivered to the window that called setRect or setSize, or to the mainframe whose size changed as a result of a system command. The screen window receives this message every time the screen resolution changes (as returned by MApp::screen().getSize()) or the screen's work area changes (as returned by MApp::screen().getWorkRect()), e.g. if the taskbar is shown or hidden.

Revealer getSize returns the size of the window after the change (getRect().size() can also be used, but getSize() is more convenient).

Default handler repaints the entire window.

This message is usually (but not necessarily) preceded by RectChanging, which allows to adjust or even to cancel the operation.

See full description and example in Resized and setRect references.

3.5.6. Moved

Generated after the position of a window changes relatively to its parent's top-left corner, as a result of a setRect function or its derivatives (setPos, bringForward, or sendBackward, although the last two don't generate messages at this time), or as a result of a system command (system commands can affect only mainframes and the screen), or internally by Elgrint. If the size changes due to a system command, the message is queued, otherwise it is stacked (handled before setRect returns).

Delivered to the window that called setRect or related function, or to the mainframe whose position changed as a result of a system command. The screen window should not receive this message at all because the screen's position is always (0,0).

Revealer getPos returns the position of the window after the change (getRect().p1() can also be used, but getPos is more convenient).

This message is usually (but not necessarily) preceded by RectChanging, which allows to adjust or even to cancel the operation.

See full description and example in Moved and setRect references.

3.5.7. ChildListChanged

Generated internally by Elgrint when one or more child windows is opened (created) or closed (destroyed), using the MWindow constructor, destructor, close, or MPtr::reset. The message is always queued, and multiple messages for the same parent window are merged into one (that's more efficient than having one message per each change).

Delivered to the parent window of the created/destroyed child.

Revealers getChild and getChildCount enumerate the children after the change. The new children are always added at the end of this list.

See full description and example in ChildListChanged reference.

3.5.8. Paint

Preface:

This message is responsible for the entire visual appearance of the app windows (except for MMultimediaLabel, which works somewhat differently). This message should be implemented for every visible window, otherwise the window will be empty (usually filled with the background color).

Generated by the system whenever a window needs to be repainted. A window need to be repainted if at least one pixel of its visible surface was "invalidated" (undefined). Pixels can be invalidated for many reasons, for example if a window is enlarged, moved into the screen, revealed from under another window, and so on. Window may be repainted even for no apparent reason at all, if the system suddenly decides to refresh the screen. Pixels can also be invalidated programmatically using the repaint function. This message is always queued, which (among other things) prevents multiple repaints, if repaint is called many times within the same loop iteration. Therefore, the repaint function only queues the message - it returns without actually repainting anything.

Digger digClipRect returns the bounding rectangle (in window coordinates) of all the invalidated pixels. This information is not essential, but using it can make OnPaint more efficient, which might be important, because repainting is usually a very expensive operation.

The OnPaint handler should be used only to draw the graphic content of the window, because this handler can be called unexpectedly for too many reasons. In particular, windows can be repainted in a random order. Creating windows or calling repaint is forbidden during OnPaint. Other operations are not forbidden, but are not recommended either.

The drawing is done with the special drawing functions, as follows:

Line/curve drawing functions:

setDrawSettingsSet color, thickness, and style of lines in the following 4 functions
drawRect(rect)drawRect.png
drawArc(center,size,angles)drawArc.png
drawPoly(p0,p1,...)drawPoly1.png
drawPoly(points)drawPoly2.png

Note: The "poly" (as in drawPoly above and fillPoly below) is a relatively unusual (but powerful) combination of polyline and polycurve. It's made of separated straight lines connected by Bezier curves. It may take a little effort to get used to it, but once you do you will be able to draw complex forms using this one function (the first drawPoly is just for convenience). You can read more in MWindow::drawPoly.

Fun Fact: If you specify only 2 points in drawPoly, it will draw a single straight line between those points, so a separate drawLine function is redundant.

Fun Fact: If you specify only 1 point in drawPoly, it will draw a single pixel at that point, so a separate drawPixel function is redundant.

Filled shapes drawing functions (identical to drawXXXX above, but fill the shapes with a linear gradient, not outline them):

setFillSettingsSet gradient fill parameters in the following 4 functions
fillRect(rect) fillRect.png
fillArc(center,size,angles) fillArc.png
fillPoly(p0,p1,...) fillPoly1.png
fillPoly(points) fillPoly2.png

Note: A gradient fill is a gradual change from one color to another along some imaginary axis. setFillSettings specifies the 2 colors and the direction of that axis.

Fun Fact: Gradient can simulate a 3-D effect, if both colors have the same hue but different lightness (the darker color represents a shadowed side), like in the gray rectange above (the fillRect illustration).

Fun Fact: If you specify only one color (the other is SameColor by default), or if both colors are identical, then the gradient becomes a solid color, which is actually the most often used option.

Complex drawing functions:

Note: The drawing functions affect only the invalid pixels inside digClipRect - valid pixels don't change even if you try to redraw them.

The appearance of the window should reflect its state, so the following window functions are often used during painting: isHot, isEnabled, getEnabling, getRect, getSize, and getPos. Also, the following properties are often used: RTL, PaintOrigin, BackColor, ForeColor, and Font. The property values are applied by default (except RTL, which has no effect on the drawing functions). Although these parameters and properties can be ignored, it is recommended to use them as much as possible to make a window's appearance more dynamic and meaningful.

All the drawing functions can be used outside of OnPaint as well, but that rarely makes sense (except for calcDrawTextSize), since any effect of such drawing will disappear when the window is repainted via OnPaint, which can happen at any moment for various reasons. One possible reason for drawing outside of OnPaint is to create some quick animation.

See full description of the drawing functions in Painting functions reference.

Bonus: drawing transformations

The final drawing function that he haven't talked about is setDrawTransform, which receives a parameter of type MTransform, which we skipped in Chapter 2 while going over structs. Transformation is a long and somewhat complicated subject in itself, but the good news is that it is not essential - it's needed mostly for convenience (and possibly for better performance). Basically, the transforms spare you some manual calculations, making it easier to implement OnPaint, especially if it requires unusually complex drawing.

If you're still interested, you can read the Drawing transformation article in the reference. But you may want to master the basics first.

Default OnPaint handler fills the entire window with the background color (specified by the BackColor property).

See full description and example in Paint reference.

3.5.9. Notice

Preface:

All the messages we have seen so far apply to any window class (and all the messages we will see later are relevant to the app in general, not to a specific window). But a window class may also have specific events, which are meaningful only for that class. For instance, "caret moved" event is meaningful only for MEditBox, "window scrolled" - for MScrollBar, "border width changed" - for MFrame, and so on. The Notice message is used to report all such class-specific or custom events.

Notices can be event-based - occur as a result of other messages (for example, "caret moved" is the result of a KeysEntered message), or property-based - purely programmatic (for example, "border width changed" is a result of MFrame::setBorderWidth function). On average, each Elgrint window class defines 4 notices of both kinds (event-based and property-based), so it's not hard to remember them. Notices can be of any message type (post-note, pre-note, or request), but all the predefined notices in Elgrint are post-notes except "Close" of MFrame and "DropDown" of MDropButton which are requests. In any case, the type of a particular notice can be deduced from its name using the same principle as for the message names (ed ending means post-note, etc.). Appendix A below lists all the notices for all the window classes in Elgrint.

A notice is defined as a pair of string (notice name) and number (notice ID), so it could be identified and properly handled. The origin of the notice (as returned by digOrigin) can also be used for identification. Often just one of these three parameters is enough, but sometimes two or even all three are required.

Generated by genNotice function. The genNotice function receives a notice name (string) and assigns a predefined numeric ID to this name. If ID is None (default for all notices) the function does nothing. So, to activate the notice, its name has to be assigned a working ID first (Auto, or anything between 0 and MaxNoticeID), using setNoticeID function. This assignment affects only one window. setNoticeID is called automatically by all window constructors that have an nidXXXX parameter, which specifies a primary notice (see Appendix A for the list of primary and other notices). genNotice is also called automatically any time a window property changes (see Appendix A for the list of properties). Of course, setNoticeID has to be called to activate these notices as well. This message is always stacked (handled before genNotice returns), with the exception of the "Close" notice of MMainFrame, which can be system-generated, and thus queued (although that doesn't happen often).

Revealers digNN and digNID return notice name and notice ID respectively, as was determined by genNotice that generated the event. getNoticeID can also be used to retrieve an ID for a given name. Note, however, that getNoticeID(digNN()) returns the current ID, while digNID() returns the ID as it was when the notice was generated. So, these two calls may produce different results. Generally, you should only use digNID to identify a notice. getNoticeID can be used for other purposes, such as temporarily deactivating a notice:

The nnXXXX constants are the names of all event-based notices and some property-based notices. In the above example we could use nnPushed instead of "Pushed". The result is the same, but nnXXXX constants are more efficient and reliable (prevent spelling mistakes) than string literals.

Default handler propagates the message to the parent window.

See full description and example in Notice reference.

3.6. System messages

Five messages, generated by events outside of this app (by other apps or by the system), although RemoteNotice can be generated from this app as well.

Delivered to the screen window only. MScreen class cannot be overloaded (no public constructor), so the only way to handle these messages is to propagate them to some other window by calling requestPropagation(mcXXXX,psScreen) of that window, where XXXX is the name of the message to propagate.

Note: MMainFrame constructor automatically calls requestPropagation for all the system messages, so if you're handling a system messages in a mainframe window (which is a typical scenario), you don't have to call requestPropagation.

Note: All system messages are always queued.

3.6.1. RemoteNotice

Generated by a call to MApp::genRemoteNotice. Similar to the Notice message, but works between threads and apps, not between windows. Also, unlike Notice, the notice name is predefined (nnThread or nnApp) and the notice ID is determined directly during the call to genRemoteNotice, not by a separate call to setNoticeID.

Revealers are the same as in Notice: digNID returns the numeric ID, specified when genRemoteNotice was called, and digNN returns nnThread ("Thread") if the message was sent within the same app, and nnApp ("App") if it was sent by a different app.

See full description and example in RemoteNotice reference.

3.6.2. DirectoryChanging

Generated when the user initiates a "soft drive removal", e.g. activates the "Safely Remove Hardware" option and stops an active device in a non-fixed drive, such as a USB disk. The drive remains active throughout the handling of this message, but it should be handled very quickly (less than a second) before the drive is deactivated.

Revealer digDir returns the root directory of the drive being deactivated. This message can be used for safe recovery of the app's state, if it depends on the data on that drive. For example, if the app performs some lengthy file writing, it should stop this operation and close all the affected files before they become inaccessible, to prevent file corruption.

See full description and example in DirectoryChanging reference.

3.6.3. DirectoryChanged

Generated when the user completes a "soft drive removal", e.g. runs the "Safely Remove Hardware" option, or physically removes an active data carrier from a non-fixed drive, such as a USB disk or a CD/DVD. It can also happen on its own, if a drive suddenly fails or becomes disconnected for some reason (especially if it's a network drive). The drive is inactive during the handing of this message and all the data on it is inaccessible. This message may be preceded by DirectoryChanging, but not necessarily.

Revealer digDir returns the root directory of the drive that was deactivated. This message should be used to recover from the loss of the data as much as possible, if the app depended on the data on that drive.

Fun Fact: in the future versions, this message may also report changes to any directory or some selected directories, not just drive roots.

See full description and example in DirectoryChanged reference.

3.6.4. SystemSuspending

Generated when the user initiates a sleep mode or shuts down the computer. It can also happen on its own, depending on the automatic power saving configuration. The system remains active while this message is being handled, but it should be handled quickly before the system is deactivated. This message can be used to stop some lengthy operation, and/or to save important information to a temporary file, so it could be restored once the system resumes activity. Note that there is no way of knowing whether the system goes to sleep or shutting down. Any conceivable handling of this message is the same in both cases.

See full description and example in SystemSuspending reference.

3.6.5. SystemResumed

Generated when the user reactivates a sleeping/hibernating system, for example by briefly pressing the computer's power button. The app can use this message to resume some lengthy operation, such as file copying, which was stopped previously due to SystemSuspending message.

See full description and example in SystemResumed reference.


These are all the messages that are defined by Elgrint. Appendix B contains some additional information for every message, including a full set of associated revealers and diggers.

3.7. Example: Calc 1.5

Now let's apply what we have learned to our Calc app.

3.7.1. Message processing in details

Recall that last time the following function was added to the CalcBar class:

Now we finally have all the knowledge to understand exactly what this function does and how it works, step by step:

So far none of the called handlers returned yet, so the message stack looks like this:

CalcBar::OnNotice
MButton::OnNotice
MButton::OnKeysEntered

The above description may appear complicated at first, but don't worry - most of the time you don't have to analyze the message processing in such detail. The important thing to remember that such a complicated and powerful activity is the result of just a few lines of code. This is good - it means that we don't have to write a lot of code to implement complex behavior.

3.7.2. The complete calculator

The above example was based on a simplistic OnNotice handler, which didn't really calculate anything. Now that we know about all the messages, let's really fix this handler, and add some others as well.

Since the "Calc" app is about to become larger, let's take the function bodies out of the CalcBar declaration, so we could go over them one by one (this is a good practice anyway):

Calc15.cpp: CalcBar declaration + MAppMain

Note: The ON_XXXX macros include the parentheses ("ON_XXXX = void OnXXXX()"), so writing ON_XXXX() would be an error.

All that's left now is to implement the 7 declared functions (including the constructor).

Calc15.cpp: constructor implementation

Note: The only change of this constructor from the previous version is that the initial rect of the buttons is set to AutoRect. This is because OnResized takes care of the buttons' placement and resizing, so the initial rect is irrelevant (can be any value, actually).

Calc15.cpp: processInput implementation

The above function implements the entire logic of the calculator. It's not important to understand it right now, because it has little to do with windows and messages. Of course, it is essential for the app itself.

Calc15.cpp: OnKeysEntered+OnStringEntered implementations

Note: The support for Esc (reset calculation) and Enter (finish calculation) could be done via OnStringEntered instead of OnKeysEntered, because Esc and Enter keys also type characters (MChar(27) for Esc). Remember that these messages are delivered to the focus window, which is the bar, not the mainframe, but the bar propagates them to the mainframe, its parent, otherwise these handlers would have had no effect.

Calc15.cpp: OnFocusChanged implementation

Note: This overload is not essential - all it does is to remove the focusing marks from the buttons by "stealing" the focus from them. Remember that when the user clicks a button (or any other window), it becomes focused and receives this message. The mainframe passes the focus to its bar plugin (the CalcBar) by default. When a button is focused, the CalcBar receives "FocusChanged" because it's lost the focus to the button. It uses this message to return the focus to itself (by calling setFocus), but only if it sees that the focus was indeed passed to a button (by using MApp::focus and the is function template).

Calc15.cpp: OnResized implementation

Note: This handler adjusts the buttons and the display to the new size. Try resizing the previous version of the calculator (by dragging mainframe borders) - you will see that buttons ignore any change. After you compile the new version, try resizing it and see what happens.

Calc15.cpp: OnNotice implementation

Finally, we get to the most important handler, the one that responds to all the buttons. But since we already have the processInput function, which takes care of all the logic, all we need here is to call this function. To make our app more reliable, we call processInput only if the notice originates in the child button (whose parent is this bar). This isn't really necessary, because the bar shouldn't receive any other notices (you didn't activate anything else, right?), but just in case a future version should activate other notices, we want to process them using the base class (MScrollBar) handler.

And there you have it - a fully functional calculator with 4 basic operations, full keyboard support, resizing auto-adjustment, custom icon and caption, and all that in some 130 lines of code (excluding comments and empty lines). With some experience, such an app can be written from scratch in 1-2 hours.

Here is the complete source code that you can copy+paste to the compilation service and compile right now:

Calc15.png

Of course, this simple calculator app is nothing compared to what Elgrint can do. A complex app may require more than one file and more than one overloaded class.

3.7.3. Bonus: improving visual design

Our calculator works fine now, but its appearance is not exactly "production quality". Of course, if you create apps for yourself or for a specific client, it doesn't matter much how the app looks, as long as it does its job. But if you want your app to get public attention (or just to create something good-looking), you should invest more effort in the visual design. As a demonstration, let's repaint the calculator buttons to make them more appealing. For this all we need is to overload the OnPaint handler of the MButton class, and as you know, overloading means declaring a new class (derived from MButton in this case):

Note that like CalcBar the new CalcButton class has the "standard-form constructor" (with parent and rect parameters), which means it can be used with MPtr. We will need that in a moment.

Exercise:

Incorporate the new button design to "Calc 1.5".

Calc16.png

The calculator looks better now. You can play with the colors - they are obviously not the best choice. Hint: look for setBackColor and setForeColor functions in the code.

Of course, this is just the first step. We can also repaint the mainframe itself (by creating a class derived from MMainFrame) and the display box. We can add more functions to the calculator, such as the square root (this would require expanding the processInput function a little), and more. You can see one possible end-result of such improvements in the Miranor Store.


As we have said in the beginning, this guide includes almost all of the essential parts of the Elgrint interface. The complete description of every public unit of the interface (and more) is available in the Reference. Also, Appendix A and B below summarize the most important aspects of the window classes and the messages respectively.

Thank you for getting this far, and have fun developing apps.

Don't forget to fill the Feedback form if you haven't done this already.


Appendix A. Window classes

WindowClasses.png

Additional information:
Name[2] Kind[3] Notice names[4] Properties[5] Propagations Used timer IDs
MWindow Container RTL, HighQuality, OS, HotCapture, CursorImage, BackColor, ForeColor, Font
MProgressLabel Label Progress, Animated, Border 1000: animation
MInfoLabel Label Text, DistFromCursor (mcKeysEntered,psAll), (mcHotChanged,psAll), (mcCursorMoved,psAll) 1000: auto-close
MMultimediaLabel Label MediaStateChanged Stretched, Volume (mcResized,psScreen)
MButton Selector Released, Pushed Text, Icon, Chosen, BorderColor, ChosenMarkColor 1000: repeated click
1001: info label
MCheckButton Selector (Released), (Pushed), Checked Tristate, RadioStyle, CheckMarkSize, CheckMarkColor (1000): repeated click
(1001): info label
MDropButton Selector (Released), (Pushed), DropDown Vertical, SplitLength (mcFocusChanged,psDescendants) (1000): repeated click
(1001): info label
1002: block re-drop
MRadioBox Selector Selected (BackColor), (ForeColor), (Font), (CursorImage)
MDateBox Selector Selected Range
MSlideBox Selector Selected MaxValue, Vertical, FreeTracking, RailBreadth, TickMarkDelta, LineScrollDelta, PageScrollRatio, ButtonLength (mcResized,psChildren), (mcFocusChanged,psChildren), (mcKeysEntered,psChildren), (mcCursorMoved,psChildren) 1000: repeated click
MRangeBox Selector Selected Range, SmallDelta, BigDelta
MSegmentBar Container Tracked Vertical, FreeTrack, TrainTrack, DividerLength, (BackColor), (ForeColor), (Font)
MPageBar Container PageSwitched (BackColor), (ForeColor), (Font), (CursorImage)
MScrollBar Container Scrolled, ScrollSizeChanged Draggable, VLineScrollSize, HLineScrollSize, VPageScrollRatio, HPageScrollRatio, AutoHideSliders
MMenuBox Selector (Scrolled), (ScrollSizeChanged), Selected, Highlighted ItemHeight (mcFocusChanged,psDescendants) 1000: sub-menu open
1001: auto-scroll
MTreeListBox Selector (Scrolled), (ScrollSizeChanged), Selected, Expanded, Collapsed, ContentChanged Separator, ItemHeight, LevelIndent, Connectors, RevealObscured, SelectedNoticeDelay, (Font) 1000: delayed notice
1001: auto-refresh
MListBox Selector (Scrolled), (ScrollSizeChanged), Selected, FocusItemChanged, ContentChanged ItemHeight, MultiSelect, ReselectOnKey, RevealObscured (mcHotChanged,psChildren) 1000: auto-scroll
MEditBox Selector (Scrolled), (ScrollSizeChanged), Finished, Modified, Highlighted, CaretMoved Multiline, ReadOnly, PasswordMasking, GapBetweenRows, MaxUndoCount, (Font) 1000: caret blinking
1001: auto-scroll
MHtmlBox Selector ReadyStateChanged
MFrame Container RankChanged, Close Resizable, RankingDuration, Caption, Icon, TitleHeight, BorderWidth, (BackColor), (ForeColor) (mcFocusChanged,psDescendants), (mcResized,psChildren)
MDialogBox Selector (RankChanged), (Close)
MFileDialogBox Selector (RankChanged), (Close)
MMainFrame Container (RankChanged), (Close), Flashed ScreenSaverAllowed, (Icon), (Caption) (all system,psScreen), (mcKeysEntered,psScreen), (mcResized,psScreen)
MScreen Container StatusIcon

Appendix B. Messages

Name[6] Group Type[7] Q?[8] Relevant revealers[9] Relevant mutators[9] MWindow handler
KeysEntered input post yes digKeys tab-stop switch + propagate to parent
StringEntered input post yes digStringRef propagate to parent
CursorMoved input post yes getCursorPos (nothing)
TimerExpired input post yes digTimerID, getTimerDelay setTimer (nothing)
FocusChanged presentation post yes/no isFocused, MApp::focus setFocus[10] pass focus to parent
HotChanged presentation post yes/no isHot, MApp::hot, isHotCapture setHotCapture (nothing)
EnablingChanged presentation post no getEnabling, isEnabled enable, disable full repaint
RectChanging presentation pre no digRectRef, getRect setRect functions[11] (nothing)
Resized presentation post yes/no getRect, getSize setRect, setSize full repaint
Moved presentation post yes/no getRect, getPos setRect functions[11] except setSize (nothing)
ChildListChanged presentation post yes getChildCount, getChild (constructor), (destructor), close (nothing)
Paint presentation request yes/no digClipRect, paint functions[12] repaint, paint functions[12] fill with BackColor
Notice presentation custom yes/no getNoticeID, digNXX setNoticeID, genNotice[13] propagate to parent
RemoteNotice system custom yes digNXX MApp::genRemoteNotice (nothing)
DirectoryChanging system pre yes digDir (nothing)
DirectoryChanged system post yes digDir (nothing)
SystemSuspending system pre yes (nothing)
SystemResumed system post yes (nothing)

Additional MWindow revealers (relevant to all messages): digMessageCode, digOrigin, isPropagationRequested.

Additional MWindow mutators (relevant to all messages): requestPropagation, cancelPropagation, propagateMsgTo.


Appendix C. Using Elgrint locally with Microsoft Visual Studio

Note: You don't need to repeat the abovementioned steps every time - it may be easier to use the project you created as a template for all of your Elgrint-based apps.


Appendix D. Using Elgrint locally with MinGW 4.8 (command-line, no IDE)


Notes:

Thank you for reading this guide.

Your feedback is important to us and it will help us improve this Guide in particular, and Elgrint in general.

If you'd like to help us, please go to the Feedback form now.

Remember that you can get credit points for useful feedback (see Contributions for details).

Miranor Home | About Miranor | About Elgrint | Create account | Login | Account settings | Contact Us | Privacy Policy | Site map

© Copyright 2014 by Miranor. All rights reserved. By using this site you agree to the Terms of Use.

Page last updated on August 10th, 2014.

Real Time Web Analytics