Component Enhancement:  TWrapGrid (Rev 3.32) 
By: David Richards <moriarty@foothill.net>


Dave has really outdone himself this time!  To say TWrapGrid is an enhancement of the TStringGrid control is simply an understatement.  This component provides the much needed features that TStringGrid lacks, and whole lot more.  Dave started from the ground up, deriving two new classes, TWrapCells amd TWrapGridEditor.  Not only is this a fully working, robust component, it is an excellent source to learn from.   If you've ever wondered how to enhance an existing VCL component, this is your chance.  Here's a brief explanation from the author himself:


The easiest place to start is probably just drop a TWrapGrid on a form, and place a TStringGrid beside it. Take a look at the properties and events in the Object Inspector - that'll give you an idea of the new published capabilities. Next, look in the header file at the TWrapCells class. It's properties will give you an idea of what the WrapCell capabilities are.

Most of the TWrapCell properties have counterparts at the grid level which are accessible through the Object Inspector. These values become the default for the WrapCells when they are instantiated at runtime. ResetWrapCells() will reset the WrapCell properties back to the values held in the grid itself, so you can change characteristics en masse by changing the grid value and then calling ResetWrapCells().

It is, in most cases, necessary to repaint the grid to get changes to cell text values to properly appear. As the TWrapCell code indicates, I had originally placed 'Paint()' commands inside of each TWrapCell property Setter, but this causes too much flicker if you're loading a number of cells at once.

Anyway, drop a grid on a form, set AutoAdjustHeight to true, WordWrap to true, and select goEditing from the Grid Options. Run it and edit a cell text string. (Oh, by the way - the TStringGrid::Cells property is ignored - the text values are stored in WrapCell[Col][Row]->Text.)
 

TWrapGrid ver 3.3.2 -- (28 KB)
 


Unlike most VCL controls, TWrapGrid comes with very complete documentation.  Here's an excerpt from the introduction and enhanced properties sections:



 
 

TWrapGrid
Ver. 3.3


Introduction
TWrapGrid is a TStringGrid descendant I've developed over the last few months. It's primary feature is the introduction of an array of TWrapCell objects, and the use of information contained therein to allow the user to customize the appearance of individual cells in the grid, including word-wrapping the text. This dynamically maintained array of TWrapCell objects is associated with the inherited TStringGrid Objects property, simplifying operations like moving columns. By overriding the inherited Paint and DrawCell methods, data contained in the TWrapCells is used to provide control of the color, font characteristcs and other properties of each cell, and the DrawText API call is used to provide the desired text formatting. As the inherited grid edit component (TInplaceEdit) is descended from TCustomEdit, it doesn't provide access to the font or color characteristics. To overcome this shortcoming I replaced the TInplaceEdit with a TMemo descendant, and exposed a number of it's properties and events through the grid interface. You can pretty well do anything you want with the appearance of the grid, and if additional capability is desired (say the addition of graphics images within each cell...) it is a simple matter to add a new data member to the TWrapCell class and modify the TWrapGrid Paint and DrawCell methods as necessary to implement the new behavior. 
 
8< - -  <snip>  - - -
 

2.2  New Public Properties

2.2.1 int   ActualHeight {read = CalculateHeight}

When a new string is loaded into a WrapCell[][]->Text location and wordwrapping and AutoAdjustHeight are enabled, the new height of the grid can be obtained from this property. This makes it possible, particularly in conjunction eith the new OnPrePaint event, to adjust the position of other controls on the form to accomodate the new grid size. (I put this in but ended up not using it - it may be useful to someone else.)

2.2.2 int ActualRowHeight[int Index] {read = GetActualRowHeight}

As with ActualHeight, this enables you to obtain the new size of a row prior to it's being painted. This is opposed to the inherited RowHeights[] property - if you load a string that changes the height of a row, prior to painting RowHeights will contain the current (old) height, and ActualRowHeights will contain the new row height.

2.2.3 int CellCols {read = FCellCols}

The current number of columns in the TWrapCell array. (Hopefully this matches the  number of columns in the grid.)

2.2.4 int CellCount {read = FCellCount}

The current number of cells in the TWrapCell array.

2.2.5 int CellRows {read = FCellRows}

The current row count of the TWrapCell array.
 

2.2.6 int EdCol {read = GetEdCol}

When the editor is visible, this holds the column index of the cell being edited; otherwise it returns -1.

2.2.7 int EdRow {read = GetEdRow}

When the editor is visible, this holds the row index of the cell being edited; otherwise it returns -1.

 2.2.8 AnsiString EditText {read = GetEditorText, write = SetEditorText}

This property allows access to the contents of the editor, in AnsiString format. It's just a wrapper for the TMemo Text property.

2.2.9 bool EditorVisible {read = FEditorVisible}

True when editor is visible, false otherwise. (You probably could have guessed...)

2.2.10 int FixedColCount {read = FFixedColCount}

This returns the number of currently defined FixedColWidths (see below).

2.2.11 int FixedColWidths[int Index] {read = GetFixedColWidth, write = 
      SetFixedColWidth}

If column sizing is allowed and word wrapping enabled, a potentially undesirable effect occurs when a column width is reduced to minimum; the height of the row grows to accomodate the increasing number of word wraps. This property allows you to set width values that will be respected by the wrapping feature, regardless of the actual width of the column. So, if you have a string that wraps once with a column in it's default width, the user can resize the column below that width and the text will be clipped at the edge of the cell, rather than increasing the number of wraps.

The 'FixedWidth' property must be set true for the data contained here to be used; otherwise it is ignored.

The data is stored in a dynamic array and must be loaded in ascending order, with no empty slots. Once the array has been created the various members can be accessed/modified randomly. The array is cleared/deleted with the 'ResetColWidths' method. (Dunno why I did it that way. <g>)

2.2.12 bool FreezeFrame {read = FFreezeFrame, write = SetFreezeFrame}

This allows you to temporarily disable the wrapping and AutoAdjustHeight capabilities. I had a situation where I wanted the contents of some of the cells to change as the user selected different values in an embedded ComboBox (see TSelectWrapGrid), but I didn't want the height of the grid or any of the rows to change until the mouse up event, signalling that the user had made a final selection. This made that possible. 

I have to apologize for the property name. Every time I tried to think of different one, that damned J Giles tune started playing in my head. I finally gave in.

2.2.13 TWrapCells*  WrapCell  [int Col][int Row] {read = GetWrapCell,
                                                            write = SetWrapCell}

This is the heart of the extended capability. All of the TWrapCell properties are accessible at runtime through this property. The text property of the cell at column 2, row 3, on the component named 'Grid1' would be accessed as 
'Grid1->WrapCell[2][3]->Text'.
 

8< - -  <snip>  - - -
 

3.2  New Published Properties
 

3.2.1 bool AutoAdjustHeight  {read = FAdjustHeight, write = SetAdjustHeight}

If this is set true and WordWrapping is enabled, the grid height will adjust itself to accomodate changing cell/row heights. I tend to enable or disable this and WordWrap at the same time. Perhaps I should eliminate this property and make it hard-wired behavior?

3.2.2 bool AdjustSizedCols  {read = FAdjustSizedCols, write = FAdjustSizedCols}

If the grid option ColSizing is selected and this is enabled, the column sizes will adjust themselves a column has been resized. The columns will fill the grid but not overflow off the side.

3.2.3 TAlignment Alignment  {read = FAlignment, write = FAlignment}

Yes, you can set text alignment, using the standard enumerated TAlignment values of taLeftJustify, taCenter and taRightJustify. This property is loaded into the TWrapCells as one of the default values in ResetWrapCells.

3.2.4 TColor EditorColor  {read = FEditorColor, write = FEditorColor}

This sets the background color of the inline editor. This property is loaded into the TWrapCells as one of the default values in ResetWrapCells.

3.2.5 TColor EditTextColor  {read = FEditTextColor, write = FEditTextColor}

This sets the text color of the inline editor. This property is loaded into the TWrapCells as one of the default values in ResetWrapCells.

3.2.6 bool  FixedWidth  {read = FFixedWidth, write = FFixedWidth}

This enables the use of values loaded into the FixedColWidths array. When true, and if column widths are defined, this will fix word-wrapping as if the cell were at this width, even if the cell is resized.
 

3.2.7 bool LastRowHidden  {read = FLastRowHidden, write = FLastRowHidden}

Have you ever wanted to use a string grid as a static display with all the cells fixed, only to be realize you can't have all the rows fixed? I've always just allowed the last row to be non-fixed, and adjusted the height of the grid so it was hidden. When the word-wrapping takes place and the height of the grid is recalculated, the last row is ignored and remains hidden if this property is set true.

3.2.8 bool MaskSelection  {read = FMaskSelection, write = SetMaskSelection}

Again, have you ever wanted to use a grid to display data but were annoyed at the clHighlight color the selected cell is painted when the grid loses focus? This flag, when set true, will hide both the select box when the grid has focus, and will ensure the selected cell is painted the default color when the grid loses focus, thus preventing that blue box look...

I've taken to setting this false in order to see the select box when the grid has focus, but setting UnFocusSelectColor (see below) to the default grid color. This way the selected cell doesn't show when focus shifts away from the grid, unless the cell editor is open.

3.2.9 TColor  SelectBoxColor  {read = FOutlineBoxColor, write = 
        FOutlineBoxColor}

This property allows you to change the color of the box that indicates the selected cell when the grid has focused, and coupled w/ SelectBoxStyle, lets you add quite a bit of variety to the select box on an individual cell basis. This property is loaded into the TWrapCells as one of the default values in ResetWrapCells.

3.2.10 TSelectBox  SelectBoxStyle {read = FSelectOutlineStyle,
                                                    write = FSelectOutlineStyle}

This allows you to select one of four different styles of selection box. They are enumerated as osThinDotted, osThickDotted, osThinSolid, osThickSolid. the default is osThisDotted. This property is loaded into the TWrapCells as one of the default values in ResetWrapCells.

3.2.11 TColor  UnFocusSelectColor  {read = FUnFocusSelectColor,
                                                   write = SetUnFocusSelectColor}

This property allow you to change the color a cell is painted when it's selected but the grid isn't focused. This property is loaded into the TWrapCells as one of the default values in ResetWrapCells.

3.2.12 TColor  UnFocusSelectTextColor {read = FUnFocusSelectTextColor,
                                                write = SetUnFocusSelectTextColor}

If you're gonna change the UnFocusSelectColor, you better be able to change the text color as well. This property is loaded into the TWrapCells as one of the default values in ResetWrapCells.

3.2.13 bool WordWrap {read = FWordWrap, write = FWordWrap}

If true the text in a cell is wrapped, up to the number of times specified in WrapLimit. This property is loaded into the TWrapCells as one of the default values in ResetWrapCells.

3.3.14 int WrapLimit {read = FMaxWraps, write = SetWrapLimit}

This limits the number of times text will wrap within a cell. The default is 20, and the constant WRAP_LIMIT places an upper boundary on the property WrapLimit of 200. 
 

8< - -  <snip>  - - -
 

5.2  TWrapCells Properties

( Just in case it hasn't been made clear, each of these properties, accessed via the grid's WrapCell[][] property, may be customized on a cell by cell basis. )

5.2.1 TAlignment  Alignment  {read = FAlignment, write =SetAlignment}

Text alignment, specified by the enumerated values taLeftJustify, taCenter and taRightJustify.

5.2.2 TBlobBvlMd  BlobBevel  {read = FBlobBevel, write = SetBlobBevel}

I hadn't mentioned this yet. As an additional way to associate a color with a row, column or cell in the grid, a preprogrammed rectangular LED kinda thing can be made to appear near the left side of the cell. It's dimensions are set by a couple of constants in the header file, BLOB_BORDER and BLOB_WIDTH. This property allows you to set the bevel style, enumerated as bbLowered and bbRaised. 

5.2.3 bool  BlobEnabled  {read = FBlobEnabled, write = SetBlobEnabled}

This property determines whether or not a blob is visible in a given cell.

5.2.4 TColor  BlobColor {read = FBlobColor, write = SetBlobColor}

 The color of the cell's blob.

5.2.5 TColor  Color  {read = FtheColor,write = SetColor}

  The cell's background color.

5.2.6 TColor  EditorColor  {read = FEditorColor, write = SetEditorColor}

 The background color of the editor when this cell is edited.

5.2.7 TColor  EditTextColor  {read = FEditTextColor, write = SetEditTextColor}

 The text color of the editor when this cell is edited.

5.2.8 TFont*  Font  {read = FtheFont,write = SetFont}

 The font used to draw text in this cell.

5.2.9 TColor  SelectBoxColor  {read = FBoxColor, write = SetOutlineBoxColor}

The color used to draw the box that indicates the selected cell when the grid has focus.

5.2.10 TSelectBox  SelectBoxStyle {read = FOutlineStyle, write = SetOutlineStyle}

The style in which the box that indicates the selected cell when the grid has focus. The choices are enumerated as osThinDotted (the default, and normal grid style), osThickDotted, osThinSolid and osThickSolid.

5.2.11 AnsiString  Text  {read = FText, write = SetText}

The text string displayed in the cell. This replaces the functionality of the inherited (and ignored) property 'Cells'.

5.2.12 TColor  UnFocusSelectColor   {read = FUnFocusSelectColor,
                                                     write = FUnFocusSelectColor}

The color with which the cell is painted if it is selected but the grid isn't focused.

5.2.13 TColor  UnFocusSelectTextColor  {read = FUnFocusSelectTextColor,
                                                   write = FUnFocusSelectTextColor}

The color in which the text is drawn if this cell is selected but the grid isn't focused.

5.2.14 bool  WordWrap  {read = FWordWrap, write = SetWordWrap}

 This determines whether or not the text in this cell is wrapped.

5.2.15 int WrapLeftMargin  {read = FWrapLeftMargin, write = SetWrapLeftMargin}

In order for the editor text to appear precisely in the place of the text in a given cell, it is necessary to tweak the boundaries of the text region in the cell. The two margin properties contain information for this purpose. The values contained therein are initially set to a default of 3 (I think...), and are automatically modified via the editor's OnMarginsAcquired event and the grid's EditMarginsAcquired method. I probably could have hidden this once I had the bugs out of this routine, but just in case someone comes up with a font that doesn't work correctly I figured I'd keep them accessible.

5.2.16 int WrapRightMargin  {read = FWrapRightMargin, write = SetWrapRightMargin}

 See above.
 
 


 
 
 


 Download TWrapGrid (includes full source code)
TWrapGrid ver 3.3.2 -- (28 KB)