Classes | |
class | AspectSizable |
"size" property of their objects. More... | |
class | Place |
A class to position objects in a rectangular area. More... | |
struct | Rectangle |
Data structure for defining a rectangle. More... |
You must set the position and size of each widget after its created, or it won't be visible. The simplest runtime approach is to set the position and size of the widgets during class initialization once.
You could use the resource editor and set the position and size at compile time. Or you could use the class AspectSizable that has the needed functions to set the position and size of a widget at runtime as most of the samples do. ( All widgets derive from AspectSizable. ) Or you may be able to use the emerging Sally IDE which has the ability to generate SmartWin++ windows.
In order to respond to a changing runtime situation, you can layout the widgets at runtime. Fixed positions and size become tedious to implement, especially as the program design changes, and they do not adapt well to a changing runtime situation. You will run into cases in which screen space is either wasted or crucial data is hidden. The program may not adapt well to smaller screen sizes such as are seen in mobile devices.
Typically you create all the widgets in an init function, call a layout
function, and then pass a member function to onSized( func ) so that layout is
called for each user resize event. Note that you don't put the onSized()
call before you create the widgets, or the resize logic will encounter NULL
widgets.
void init()
{
Creation here
layout();
onSized( &HelloWinClass::isResized );
}
void isResized( const WindowSizedEventResult & sz )
{
layout();
}
void layout()
{
Actual layout code here
}
The main issue is: How should the position and size of the widget change as the application state changes ?
There are three general approaches:
You divide the Rectangle by generating smaller Rectangles using Rectangle generator functions.
Start with the client rectangle of the window, which can be retrieved with
getSizeClientArea(). Then use the Rectangle member functions to return other
smaller Rectangles. For example,
SmartWin::Rectangle r( getSizeClientArea().size );
Rectangle lowerHalf = r.bottom( 0.5 );
Rectangle lowright_quarter = lowerHalf.right( 0.5 );
Notice that lowright_quarter has 1/4 the area of r, and is its upper left is
positioned at the center of r. These also achieve the same result:
Rectangle lowright_quarter = r.bottom( 0.5 ).right( 0.5 );
Rectangle lowright_quarter = r.bottom().right();
Some other Rectangle functions are:
Rectangle left ( double portion= 0.5 );
Rectangle right ( double portion= 0.5 );
Rectangle top ( double portion= 0.5 );
Rectangle bottom ( double portion = 0.5 );
(The new rectangle's size is the old * portion)
Rectangle getLeft ( long x );
Rectangle getRight ( long x );
Rectangle getTop ( long y );
Rectangle getBottom ( long y );
(The new rectangle's size is the x or y value)
Rectangle row ( int row, int rows );
Rectangle col ( int column, int columns );
(Divides the rectangle into rows or columns, and returns the rectangle for the
zero based row or col. r2= r1.row( 1, 3) would give you the middle row.)
Rectangle subRect ( double x_fraction, double y_fraction,
double width_fraction, double height_fraction );
((Move left and down by x_fraction, y_fraction of the size. Make the new size
be width_fraction, height_fraction of the old size. )
Once you get the desired rectangle, you can use the widget method setSize(
Rectangle &r)
If its too much trouble to generate the rectangles, and then call setSize(),
there are combination methods that does row and column extraction and setSize
together:
void setSizeAsCol ( const Rectangle & rect, int rows, int
rownum );
void setSizeAsRow ( const Rectangle & rect, int cols, int
colnum );
If you keep dividing the rectangles into smaller rectangles until you have the desired position for each widget, you will have the #3 layout scheme.
Use the Place class, which sets the
The Place class has a empty constructor which corresponds to starting with a rectangle based at 0,0 and unlimited size. However you will normally use:
setBoundsBorder( Rectangle & rect, int border_x= 0, int border_y= 0 )
rect specifies the bounding rectangle, and border_x and border_y specify the desired inter-widget spacing. The first widget will therefore have x position of rect.pos.x + border_x.
positionToRight( Rectangle & obj ) is the fundamental routine that sets obj.pos, and internally advances the "current position" according to the obj.size. So obj.size is a input and obj.pos is an output of this function. Afterwards, obj is just the rectangle we want for a setSize( obj ); The method is to place the objects from left to right until there is not enough room in the bounding rectangle on this row, and then start a new row just below the previous row. Note that repeated calls for widgets of different sizes works with setPositionPerPlace(). You can start a new row with:
newRow()
This approach lets the widgets take advange of all the available space and the proportions of the window. For the last widget to be placed in the rectangle, there is a function that allocates all the remaining space available in the bounding rectangle:
sizeForRemainingSpace( Rectangle obj )
Like positionToRight(), it sets obj.pos and takes as input obj.size. You might do a NewRow() just before calling sizeForRemainingSpace().
But suppose you don't know what the best size should be for the widgets. Place has sizeOfCell( rows, cols, Point &Size ) that will set a size Point by dividing up the bounding rectangle into rows and columns, and also takes into account the broders set in setBoundsBorder(). So you can use sizeofCell to generate the size, and then positionToRight() to set the position. This situation is common enough that the AspectSizable implements the:
void setSizeAsGridPerPlace( SmartWin::Place & bound, int rows, int cols );
which takes a Place class and the number of rows/columns of the grid, calculates the desired size, places the rectangle, and then does the setSize() all in one call. There is a similar combined widget function that uses the existing size of the widget for placement:
void setPositionPerPlace( SmartWin::Place &bound );
So you would set the widget size in intialization code, and then in the layout
code call setPositionPerPlace(). To clarify, the difference bewteen the two is
that setSizeAsGridPerPlace() resizes the Widget into a grid cell first, while
setPositionPerPlace() preserves the existing size, and puts the widget into the
rectangle in a teletype manner: left to right, and then a new row as needed.
Are you tired of not having your text fit in the button ? Well, there is a AspectSizable function:
Point getTextSize( tstring & text )
that returns the dimensions of the text given the font used in the widget. You may need some extra padding to account for the widget itself, and you can use the widget function:
void setSizePerTextPerPlace( Place &bound, tstring &text, int extraX= 0, int extraY= 0 )
to combine the getTextSize() and setPositionPerPlace() functions. This is the layout approach #2. A wish list item is a way to scale the font according to the available size.
Notice that the Rectangle division functions are pure math in BasicTypes.cpp, the Place.h class knows nothing about widgets; it only sets a Rectangle position, and then updates per the Rectangle size it was just given. The Widget knowledgable portion remains in AspectSizeable.h, which is shared by all widgets.