User:Boldingd/Sandbox/GuiProgramming/First Steps: Displays, Controls and Layouts
First Steps: Displays, Controls and Layouts
Having looked at the basic concepts of event-driven programming, looked at a number of possible toolkits and considered how to start a "blank file" project, let us now turn to the building-blocks that we will use to construct graphical interfaces.
Let us consider some of the common basic widgets that we will be working with. Most GUI libraries will provide us with a selection of basic GUI elements - which might be colloquially called "widgets". In many cases, we build our interfaces by taking an abstract widget, and then packing some of these basic widgets into it.
Typically, widgets will be part of an inheritance tree, which will often be rooted at a basic, blank "widget" class; I will call this class
BasicWidget will usually represent an abstract entity that can be added to another component, laid out and drawn, and will include useful members and functions to accomplish this. Many of the concrete controls in our windowing toolkits will inherit from this basic empty widget, overriding some of its methods and members as appropriate.
Widgets will typically provide us a list of interesting 'properties' and 'events'. In most cases, these properties and events are also inherited; the empty base widget will likely provide us with a few, which will then be inherited by every other widget. For example, the
BasicWidget might provide us with a
stylesheet property and
Widgets will often have many more properties and events than we consider; most common GUI toolkits are designed to span a wide number of usage cases, and so their widgets will be very flexible. In many cases, only a very few events and properties are of primary interest, and we will typically focus on those.
BasicWidgets typically have the ability to contain other widgets as children; we will often create our interfaces by extending this
BasicWidget and adding properties, events and child widgets as needed. In this way, our interfaces can be viewed as 'trees', which will be rooted at some top-level widget that represents a window and will follow through other widgets that contain other widgets that contain other widgets, eventually ending at basic widgets like buttons and labels.
This arrangement suggests three families that we can group most specific widgets into: 'containers', 'controls' and 'displays'. 'Containers' are widgets whose primary purpose is to contain other widgets as children - and likely also to lay them visually on screen; 'controls' are widgets whose major purpose are to allow the user to issue commands to the software or to specify useful values; 'displays' are widgets whose primary purpose are to allow the software to communicate results back to the user. The distinctions are not always clear-cut; some widgets might be both a control and a display, for example, or a control and a container.
Before we move on, it is worth noting that BasicWidgets have one other use. Typically, they can be placed into other widgets and displayed, and they have no particular size constraints - in particular, no minimum and maximum sizes. Therefore, we can use empty BasicWidgets as "spacers" in layouts, which we will consider below.
It is also worth noting that another way that we can create our own custom widgets is to inherit from
BasicWidget and provide our own drawing logic, typically by overriding some provided
draw method. However, this method of developing custom widgets is much less common (in part because it is more labor intensive) than simply packing a BasicWidget with useful controls that the toolkit provides for us.
Two Basic Widgets: the Button and the Label
Perhaps the minimal display widget - and perhaps the simplest useful widget baring the BasicWidget - is the Label. Labels are simple widgets that display a given text-string wherever they are placed in our interface; they are primarily useful for (as their name implies) labeling some other component of the interface. The label has no interesting events and only a single interesting property, the text-string that it contains.
Labels are not particularly interesting on their own; the user cannot interact with them (other than perhaps to select and copy their text), and we typically will not be acting on them after we create them to display information to the user. Of more interest is the Button; much as the Label is our simplest 'display' widget, the Button is perhaps our simplest possible 'control' widget. Like the Label, Buttons will generally have a single interesting property, the text string that they display; however, unlike Labels, Buttons provide us with an interesting event - the
on_click event. When the user clicks the Button, the Button will emit the
on_click event. More technically, the system will detect that the mouse button was depressed and released in the area on the screen that the Button occupies, and will call every event handler registered to the Button's
on_click event; we usually do not concern ourselves with these details, and simply say that "the Button emits the
We will typically use Buttons to allow the user to request that we perform some action - obvious examples are the ubiquitous "OK" and "Cancel" buttons that can be found throughout most programs of any complexity, which allow the user to tell the program when they are ready to have the system perform some task or wish for the system to not perform some task. It is worth noting that we usually do not place the code that performs a given task in the callback that we will connect to a Button; rather, the actual callback in the Button will change some internal state in our interface and then arrange to have the task begun later, likely in another thread.
Having touched on two widgets of interest, a minimal control and a minimal display, the motivated reader will be tempted to begin building basic interfaces. However, we will need some other tools to build a usable interface. Simply placing widgets into an entity tree is not enough information for the system to build our interface; it will tell the system what controls are present, but it will not tell the system how those controls should be displayed - in particular, the system won't know 'where' on screen to place the controls, or, if they can be different sizes, how 'large' they should be. To provide this information, we turn to 'layouts'.
Positioning and Sizing
Widgets will typically provide 'minimum', 'maximum' and 'preferred' sizes; these sizes can depend on what the widget's intended purpose is, and what its current contents are. For example, a Button's minimum, maximum and preferred size might all be the same, and they might be determined entirely by the text content of the button (and perhaps the current 'style sheet' in use in the application); meanwhile, an empty BasicWidget might not have any maximum size, but its minimum and preferred sizes might be determined by the widgets it contains. These properties can vary both 'vertically' and 'horizontally' - a widget might have a specified maximum 'height' that it can occupy, but it might be capable of being any 'length'.
Widgets may also have different "willingness" to grow vertically or horizontally. For example, it might be the case that we have two widgets laid out in a row, and neither of them might specify a maximum length: in this case, how should we allocate the vertical space of the widget? While some toolkits might use some combination of the different sizes and some implicit rules, other toolkits provide us with 'weights'. Weights represent how much "slack space" should be given to the components in the case that a layout has more space available than all widget's minimum or preferred sizes. In some toolkits, weights are (implicit or explicit) properties of the 'widgets' while in others they are properties of the 'layouts'; the distinction is not terribly important, as they tend to perform the same role.