Ask Reuben

StretchMin, StretchMax

How do I stop a table column stretching so far that it is mostly white space? 

How do I stop a table column shrinking too much so that I can’t read anything?

If I was responsible for an organisations code base and coding standards, with the release of Genero Enterprise 4.00 and the various responsive features, one of the coding standards I would add is that table columns that a) contain character data and b) have a width greater than 15, I would say that these columns should have STRETCH=X.  Most codes and numeric values are consistent in their width and less than 15 characters, whilst most name, description, address type data are textual, vary in length and are often designed with a maximum length that is rarely encountered.  By adding STRETCH=X to these columns, they will stretch and shrink in width in response to the width of the table.

I can show this with this example, the code for which is at the end of the article. The first series of screenshots show what happens without STRETCH=X and the parent window is resized smaller and bigger.

Note how when the table is smaller, some data is obscured including the Grand Total, but there is unused space in the description column.  Note how when the table is wider, there is some white space to the right.

These next three screenshots, I have added STRETCH=X to the Description column.  Note how in response to the table getting bigger or smaller, the description column shrinks or grows.

There is no white space to the right and there is less chance of important data being obscured.

The minimum width that a column will shrink to is the number of cells used by the layout tag in the form design.  There is no default maximum constraint as to how wide a column will stretch.

The new STRETCHMIN and STRETCHMAX attributes allow you to override this minimum and maximum width that a column can shrink and grow to.

A column (for example a name or description) might have a width of 15 defined by the layout tag in the form definition. You can use STRETCHMIN to change that value, you might decide to set it so that a column can shrink further but always make sure the column title is always visible.  In this example, note how I shrunk the window, the description column has shrunk also but only so far that the title “Description” is still completely visible.

As the name implies, STRETCHMAX applies a maximum width to the column.  This will prevent the case that there is too much white space inside the Table.  In this next screenshot, it is better to have some whitespace to the right rather than in the Description column between the numeric data and the labels for each row of data.

Some additional points about these new stretch attributes.

  • STRETCHMIN, STRETCHMAX can be specified using the media size selector @ e.g. STRETCHMIN@SMALL=5, STRETCHMIN@MEDIUM=10.  This enables you to target the stretch characteristics for different devices.
  • Due to proportional fonts, the units for STRETCHMIN, STRETCHMAX do not equate to the number of characters.  Note that I did not need to set STRETCHMIN=12 in order to display the 12 characters in “Description”
  • When there are multiple columns that have STRETCH=X, they will each stretch and shrink in proportion.
  • The STRETCHMIN, STRETCHMAX currently only apply inside a TABLE container, they do not apply inside a GRID or SCROLLGRID

As I said at the start, I would consider adding STRETCH=X to certain columns as part of my coding standards.  As part of that you may wish to also have standards about the use of STRETCHMIN, and STRETCHMAX.  Perhaps STRETCHMIN set to that the column title is at a minimum visible, and STRETCHMAX set to a factor of the maximum number of characters that can be displayed to prevent excessive white space.

The code for the above screenshots is below.  Note the comments in the form about the application of STRETCH and STRETCHMIN, STRETCHMAX to the description column.

#! stretchmin.per
LAYOUT (TEXT="StretchMin / StretchMax")
TABLE
{
[f00|f01     |f02           |f03    |f04     |f05        |f06        |f07        ]
                                                                     [a07        ]  
}
END
END
ATTRIBUTES
f00 = formonly.line, TITLE="Line";
f01 = formonly.code, TITLE="Code";

-- Uncomment one of these next three lines at a time
f02 = formonly.desc, TITLE="Description", SCROLL;  -- No stretch
--f02 = formonly.desc, TITLE="Description", SCROLL, STRETCH=X;
--f02 = formonly.desc, TITLE="Description", SCROLL, STRETCH=X, STRETCHMIN=8, STRETCHMAX=50;

f03 = formonly.quantity, TITLE="Quantity", FORMAT="---,--&";
f04 = formonly.price, TITLE="Price", FORMAT="--$&.&&";
f05 = formonly.net, TITLE="Net", FORMAT="----,-$&.&&";
f06 = formonly.tax, TITLE="Tax", FORMAT="----,-$&.&&";
f07 = formonly.gross, TITLE="Gross", FORMAT="----,-$&.&&";
AGGREGATE a07 = FORMONLY.gross_total, AGGREGATETEXT = "Total:", AGGREGATETYPE = SUM;

INSTRUCTIONS
SCREEN RECORD scr(line,code, desc, quantity, price, net, tax, gross)
#! stretchmin.4gl
IMPORT util
TYPE line_type RECORD
    idx INTEGER,
    code CHAR(10),
    desc STRING,
    quantity DECIMAL(11, 2),
    price DECIMAL(11, 2),
    net DECIMAL(11, 2),
    tax DECIMAL(11, 2),
    gross DECIMAL(11, 2)
END RECORD
DEFINE arr DYNAMIC ARRAY OF line_type

MAIN

    CALL ui.Interface.loadStyles("stretchmin_table.4st")
    CLOSE WINDOW SCREEN

    CALL populate()
    OPEN WINDOW w WITH FORM "stretchmin_table"
    DISPLAY ARRAY arr TO scr.*
END MAIN

FUNCTION populate()
    DEFINE i INTEGER

    FOR i = 1 TO 10
        LET arr[i].idx = i
        LET arr[i].code = random_code()
        LET arr[i].desc = random_text()
        LET arr[i].quantity = util.Math.rand(100) + 1
        LET arr[i].price = (100 + util.Math.rand(10000)) / 100
        LET arr[i].net = arr[i].quantity * arr[i].price
        LET arr[i].tax = arr[i].net * 0.1
        LET arr[i].gross = arr[i].net + arr[i].tax
    END FOR

END FUNCTION

FUNCTION random_code()
    DEFINE sb base.StringBuffer
    DEFINE i INTEGER

    LET sb = base.StringBuffer.create()

    FOR i = 1 TO 3
        CALL sb.append(ASCII (util.Math.rand(26) + 65))
    END FOR
    CALL sb.append(util.Math.rand(100000) USING "&&&&&")
    RETURN sb.toString()
END FUNCTION

FUNCTION random_text()
    DEFINE
        word_count INTEGER,
        letter_count INTEGER
    DEFINE
        word_idx INTEGER,
        letter_idx INTEGER
    DEFINE sb base.StringBuffer

    LET sb = base.StringBuffer.create()
    LET word_count = util.Math.rand(4) + 1
    FOR word_idx = 1 TO word_count
        IF word_idx > 1 THEN
            CALL sb.append(" ")
        END IF
        LET letter_count = util.Math.rand(8) + 3
        FOR letter_idx = 1 TO letter_count
            IF letter_idx = 1 THEN
                CALL sb.append(ASCII (util.Math.rand(26) + 65))
            ELSE
                CALL sb.append(ASCII (util.Math.rand(26) + 97))
            END IF
        END FOR
    END FOR
    RETURN sb.toString()

END FUNCTION
#! stretchmin.4st
<?xml version="1.0" encoding="ANSI_X3.4-1968"?>
<StyleList>
  <Style name="Window">
     <StyleAttribute name="windowType" value="normal" />
  </Style>
  <Style name="Table">
    <StyleAttribute name="headerAlignment" value="auto" />
    <StyleAttribute name="showGrid" value="vertical" />
  </Style>
</StyleList>