cConsoleBuffer class
A grid of character cells in memory, with a colour pair for each cell and a record of the rectangle that has changed, that is drawn on the console in one call.
The drawing members change cells of the grid and nothing on the screen. Flush writes the cells that changed since the last write to the console with a single call of WriteConsoleOutputW, so a program draws a whole picture without flicker. The full-screen mode of cConsole makes one buffer, which its Buffer property returns and its Refresh method flushes. A program can also create a buffer with New, and it needs no console to do so, since the grid is ordinary memory.
Dim buf As cConsoleBuffer = New cConsoleBuffer(10, 3)
Dim ci As ConsoleCharInfo
buf.WriteAt 2, 1, "Hi", ConsoleColor.Yellow, ConsoleColor.DarkBlue
ci = buf.GetChar(2, 1)
Debug.Print ci.Char & " " & ci.ForeColor & " " & ci.BackColor
ci = buf.GetChar(3, 1)
Debug.Print ci.Char & " " & ci.ForeColor & " " & ci.BackColor
ci = buf.GetChar(0, 0)
Debug.Print "[" & ci.Char & "] " & ci.ForeColor & " " & ci.BackColor
' Output:
' H 14 1
' i 14 1
' [ ] 7 0
- The cells
- The dirty rectangle
- Batch updates
- Known problems in version 0.0.0.0
- Constructor
- Properties
- Methods
- See Also
In the syntax lines of this page, object is a cConsoleBuffer. Columns and rows are counted from 0, and a position is a column and a row, in that order.
The cells
The grid has Width columns and Height rows. A cell holds one UTF-16 code unit and one colour attribute, which is the foreground ConsoleColor plus 16 times the background. The cells are kept row after row as the CHAR_INFO structure of Windows (two 16-bit values, 4 bytes), so the cell at col and row is number row * Width + col. BufferPtr returns the address of the first cell.
A character outside the Basic Multilingual Plane, such as most emoji, is two code units and takes two cells. The buffer has no notion of the width of a character: a character that the console draws two cells wide still takes one cell of the grid.
Defaults: where a member takes fore and back and they are omitted, the cell gets Gray on Black. The members do not use the colours of the cConsole object.
The dirty rectangle
The buffer records the smallest rectangle that contains every cell changed since the last Flush. SetChar, WriteAt, FillRect and CopyRegion grow the rectangle, and so do DrawBox, DrawHLine and DrawVLine through SetChar. Clear and Resize mark the whole grid, and Flush and ClearDirty empty the rectangle. IsDirty tells whether it holds anything, and GetDirtyRect returns it. After the rectangle is emptied, its Left is Width, its Top is Height, and its Right and Bottom are 0.
Dim buf As cConsoleBuffer = New cConsoleBuffer(10, 3)
Dim r As ConsoleRect
Debug.Print buf.IsDirty ' a new buffer is all dirty
buf.ClearDirty
Debug.Print buf.IsDirty
buf.SetChar 4, 1, "x"
buf.SetChar 6, 2, "y"
r = buf.GetDirtyRect
Debug.Print r.Left & " " & r.Top & " " & r.Right & " " & r.Bottom
' Output:
' True
' False
' 4 1 6 2
WriteAt, FillRect and CopyRegion pass the rectangle they were given to the record without checking it against the buffer, so a call with a position outside the grid can leave a dirty rectangle that reaches beyond the grid. Flush then asks Windows to write a region that is partly outside the screen buffer. A row below the grid or a column beyond its width does no harm: Windows clips the region. A negative column or row does: the next Flush writes nothing, and empties the rectangle all the same, so the cells that changed since the previous flush stay off the screen until they change again or RefreshAll of the console writes the whole buffer.
Batch updates
A program that changes most of the grid in one pass can skip the bookkeeping. Between BeginBatchUpdate and EndBatchUpdate the members do not grow the dirty rectangle, and EndBatchUpdate marks the whole grid as dirty, so the next Flush writes everything. Batches nest: the grid is marked when the outermost one ends.
Dim buf As cConsoleBuffer = New cConsoleBuffer(10, 3)
Dim r As ConsoleRect
buf.ClearDirty
buf.BeginBatchUpdate
buf.SetChar 4, 1, "x"
Debug.Print buf.IsInBatchMode & " " & buf.IsDirty
buf.EndBatchUpdate
r = buf.GetDirtyRect
Debug.Print buf.IsInBatchMode & " " & buf.IsDirty
Debug.Print r.Left & " " & r.Top & " " & r.Right & " " & r.Bottom
' Output:
' True False
' False True
' 0 0 9 2
Known problems in version 0.0.0.0
Warning
In version 0.0.0.0, ScrollRegion moves a region by less than the distance asked for when the region starts closer to the top or left edge than that distance, and clears the full distance, so cells are lost without an error. A WriteAt, FillRect or CopyRegion with a negative column or row makes the next Flush draw nothing, and raises no error; see The dirty rectangle.
Constructor
New
Creates a buffer and clears it.
Syntax: New cConsoleBuffer ( width , height )
- width
- required A Long, the number of columns, 1 or more.
- height
- required A Long, the number of rows, 1 or more.
The constructor does not check the sizes. It calls Resize, so every cell holds a space in Gray on Black, and the whole grid is dirty. The buffer takes 4 bytes for each cell.
Properties
BufferPtr
The address of the first cell. LongPtr, read-only.
Syntax: object.BufferPtr
The address is that of the first element of the array of cells, VarPtr(m_Buffer(0)). It is valid until Resize is called, which allocates a new array. A program can read or write the cells through it, for example with CopyMemory; see The cells for the layout. The dirty rectangle does not follow such writes, so the program calls Flush with the flag forceAll, or FlushRegion.
Height
The number of rows. Long, read-only.
Syntax: object.Height
IsDirty
Whether a cell has changed since the last write to the console. Boolean, read-only.
Syntax: object.IsDirty
IsInBatchMode
Whether a batch update is open. Boolean, read-only.
Syntax: object.IsInBatchMode
True from BeginBatchUpdate until the matching EndBatchUpdate.
Width
The number of columns. Long, read-only.
Syntax: object.Width
Methods
BeginBatchUpdate
Starts a batch update.
Syntax: object.BeginBatchUpdate
Adds 1 to a counter. While the counter is above 0, SetChar, WriteAt, FillRect, CopyRegion and the other members do not grow the dirty rectangle.
Clear
Fills every cell with one character and one pair of colours.
Syntax: object.Clear [ fore [ , back [ , fillChar ] ] ]
- fore
- optional A ConsoleColor, the text colour. The default is Gray.
- back
- optional A ConsoleColor, the background colour. The default is Black.
- fillChar
- optional A String whose first character is used. The default is a space. An empty string raises error 5, Invalid procedure call or argument.
Marks the whole grid as dirty, also during a batch update.
ClearDirty
Empties the dirty rectangle without writing anything.
Syntax: object.ClearDirty
Sets IsDirty to False and the rectangle to its empty form. The cells that were dirty are not written by the next Flush, unless that call forces all.
CopyRegion
Copies a rectangle of cells to another position.
Syntax: object.CopyRegion ( srcLeft , srcTop , srcRight , srcBottom , dstLeft , dstTop )
- srcLeft, srcTop, srcRight, srcBottom
- required Long values, the corners of the source rectangle, inclusive.
- dstLeft, dstTop
- required Long values, the position of the upper left corner of the copy.
The source rectangle is first cut to the grid. Its left and top edges are raised to 0 and its right and bottom edges lowered to the last column and row, and the destination is not moved to match, so a source that reaches outside the grid is copied from its cut position to the unchanged destination. Cells whose destination lies outside the grid are skipped. Source and destination can overlap; the copy runs in the direction that does not overwrite cells before they are read. The method passes the corners of the destination and of the source rectangle to the dirty rectangle, which a batch update suppresses, and it does not cut the destination corners to the grid.
DrawBox
Draws the outline of a rectangle with line-drawing characters.
Syntax: object.DrawBox ( left , top , right , bottom [ , fore [ , back [ , doubleLine ] ] ] )
- left, top
- required Long values, the column and the row of the upper left corner.
- right, bottom
- required Long values, the column and the row of the lower right corner.
- fore, back
- optional ConsoleColor values. The defaults are Gray and Black.
- doubleLine
- optional A Boolean. True uses the double-line characters (U+2554, U+2557, U+255A, U+255D, U+2550, U+2551), False, the default, the single-line ones (U+250C, U+2510, U+2514, U+2518, U+2500, U+2502).
The four corners, then the top and bottom lines, then the left and right lines are written with SetChar, so the parts outside the grid are left out. The inside of the box is not changed. No error is raised for a rectangle that is one cell wide or high; the corners overwrite each other.
DrawHLine
Draws a horizontal line.
Syntax: object.DrawHLine ( col , row , length [ , fore [ , back [ , doubleLine ] ] ] )
- col, row
- required Long values, the start of the line.
- length
- required A Long, the number of cells.
- fore, back, doubleLine
- optional, as for DrawBox. The line uses U+2500, or U+2550 when doubleLine is True.
The cells are written with SetChar.
DrawVLine
Draws a vertical line.
Syntax: object.DrawVLine ( col , row , length [ , fore [ , back [ , doubleLine ] ] ] )
- col, row
- required Long values, the start of the line.
- length
- required A Long, the number of cells, going down.
- fore, back, doubleLine
- optional, as for DrawBox. The line uses U+2502, or U+2551 when doubleLine is True.
EndBatchUpdate
Ends a batch update.
Syntax: object.EndBatchUpdate
Subtracts 1 from the counter when it is above 0. When the counter reaches 0 the whole grid is marked as dirty. A call without a matching BeginBatchUpdate does nothing.
FillRect
Fills a rectangle with one character and one pair of colours.
Syntax: object.FillRect ( left , top , right , bottom [ , char [ , fore [ , back ] ] ] )
- left, top, right, bottom
- required Long values, the corners, inclusive. The rectangle is cut to the grid: a left or top below 0 is raised to 0, and a right or bottom beyond the grid is lowered to its last column or row.
- char
- optional A String whose first character is used. The default is a space. An empty string raises error 5.
- fore, back
- optional ConsoleColor values. The defaults are Gray and Black.
The two corners of the cut rectangle are passed to the dirty rectangle. A rectangle with left greater than right changes no cell.
Flush
Writes the changed cells to a console screen buffer.
Syntax: object.Flush ( hOutput [ , forceAll ] )
- hOutput
- required A LongPtr, the handle of a console screen buffer with write access. Refresh of cConsole passes the standard output handle.
- forceAll
- optional A Boolean. True writes every cell. The default, False, writes the dirty rectangle only, and returns at once when nothing is dirty.
The cells go to the same positions in the screen buffer: the cell at column c and row r of the grid is written at column c and row r, counted from the top left of the screen buffer and not of the window. Afterwards the dirty rectangle is emptied. The method does not check the result of WriteConsoleOutputW: when the call fails, because the handle is not a console, the cells are not shown and the dirty rectangle is emptied all the same. Calls WriteConsoleOutputW. Without a console, nothing is shown.
FlushRegion
Writes a rectangle of cells to a console screen buffer.
Syntax: object.FlushRegion ( hOutput , left , top , right , bottom )
- hOutput
- required A LongPtr, as for Flush.
- left, top, right, bottom
- required Long values, the corners, inclusive. The rectangle is cut to the grid.
Writes the rectangle whether or not it is dirty, at the same position in the screen buffer. The dirty rectangle is left as it is. Calls WriteConsoleOutputW.
GetChar
Returns the character and the colours of a cell.
Syntax: object.GetChar ( col , row ) As ConsoleCharInfo
- col, row
- required Long values, the position of the cell.
Returns a ConsoleCharInfo. Char holds the one character, ForeColor and BackColor the colours; Style is always 0. For a position outside the grid it returns the empty structure: Char is an empty string and both colours are Black.
GetDirtyRect
Returns the dirty rectangle.
Syntax: object.GetDirtyRect As ConsoleRect
Returns a ConsoleRect. When nothing is dirty it is the empty form described in The dirty rectangle.
Resize
Gives the buffer a new size and clears it.
Syntax: object.Resize ( width , height )
- width, height
- required Long values, the new number of columns and rows.
The old contents are lost, not copied: the method allocates a new array and calls Clear with its defaults, so every cell is a space in Gray on Black and the whole grid is dirty. Any address from BufferPtr is no longer valid. The full-screen mode of cConsole calls this method when a size event shows a new size, so a program redraws after the resize handler.
ScrollRegion
Moves the cells of a rectangle by a number of rows and columns, and clears the cells that this uncovers.
Syntax: object.ScrollRegion ( left , top , right , bottom , deltaRow , deltaCol )
- left, top, right, bottom
- required Long values, the corners of the region, inclusive. The region is cut to the grid.
- deltaRow
- required A Long. A positive value moves the content down, a negative value up.
- deltaCol
- required A Long. A positive value moves the content right, a negative value left.
The rows (or columns) that the move uncovers are filled with spaces in Gray on Black. Cells moved out of the region are lost. The method does nothing when both deltas are 0. Note the order of the two deltas: rows come first.
The method works by copying from a source rectangle that is deltaRow rows above and deltaCol columns left of the region. When that source reaches above the top of the grid, or left of its left edge, it is cut (see CopyRegion) and the destination is not moved, so the content moves by the distance between the region and the edge, which is less than the delta, while the full number of rows or columns is cleared. At the top edge a scroll down of one row therefore moves nothing and only clears the first row. Scrolling up and to the left, and scrolling down or right in a region that is as far from the top or left edge as the delta, work as expected.
Dim a As cConsoleBuffer = New cConsoleBuffer(5, 4)
Dim b As cConsoleBuffer = New cConsoleBuffer(5, 4)
Dim ci As ConsoleCharInfo
Dim y As Long
Dim textA As String
Dim textB As String
For y = 0 To 3
a.WriteAt 0, y, String$(5, Chr$(65 + y)) ' the rows A, B, C, D
b.WriteAt 0, y, String$(5, Chr$(65 + y))
Next
a.ScrollRegion 0, 0, 4, 3, -1, 0 ' up by one row
b.ScrollRegion 0, 0, 4, 3, 1, 0 ' down by one row, from the top edge
For y = 0 To 3
ci = a.GetChar(0, y)
textA = textA & ci.Char
ci = b.GetChar(0, y)
textB = textB & ci.Char
Next
Debug.Print "[" & textA & "]"
Debug.Print "[" & textB & "]"
' Output:
' [BCD ]
' [ BCD]
The second line should read [ ABC]. Moving the region down from a distance of 1 row below the top, with b.ScrollRegion 0, 1, 4, 3, 1, 0, gives the expected A BC.
SetChar
Sets one cell.
Syntax: object.SetChar ( col , row , char [ , fore [ , back ] ] )
- col, row
- required Long values, the position of the cell. A position outside the grid is ignored, and nothing is marked.
- char
- required A String. Only its first character is used. An empty string raises error 5, Invalid procedure call or argument.
- fore, back
- optional ConsoleColor values. The defaults are Gray and Black.
Marks the cell as dirty.
WriteAt
Writes a string into a row of cells.
Syntax: object.WriteAt ( col , row , text [ , fore [ , back ] ] )
- col, row
- required Long values, the position of the first character.
- text
- required A String. Each UTF-16 code unit takes one cell, including carriage returns, line feeds and tabs, which are stored as ordinary characters. Characters that fall to the right of the last column are dropped, and characters to the left of column 0 are skipped. The text does not wrap.
- fore, back
- optional ConsoleColor values. The defaults are Gray and Black.
Nothing is written when row is outside the grid. The dirty rectangle is grown by the first and the last column of the text and by row in every case, even when they lie outside the grid, and also when text is empty.
WriteTextEx
Writes a string, shortened to a width with an ellipsis.
Syntax: object.WriteTextEx ( text , col , row , fore , back , maxWidth [ , ellipsis ] )
- text
- required A String.
- col, row
- required Long values, the position of the first character.
- fore, back
- required ConsoleColor values. Unlike the other drawing members, this one has no defaults for the colours.
- maxWidth
- required A Long, the most cells to write. A value of 0 or less writes nothing.
- ellipsis
- optional A String, the mark that shows the text was cut. The default is the character U+2026, the horizontal ellipsis.
When text is longer than maxWidth, the method writes the first maxWidth - Len(ellipsis) characters of the text followed by the ellipsis, so that the result is maxWidth characters. When the ellipsis itself is as long as maxWidth or longer, only the first maxWidth characters of the ellipsis are written, and nothing of the text. Shorter text is written whole. The order of the parameters differs from WriteAt: the text comes first.
Dim buf As cConsoleBuffer = New cConsoleBuffer(20, 2)
Dim ci As ConsoleCharInfo
Dim x As Long
Dim line As String
buf.WriteTextEx "Filename.extension", 0, 0, ConsoleColor.Gray, ConsoleColor.Black, 10, "..."
For x = 0 To 9
ci = buf.GetChar(x, 0)
line = line & ci.Char
Next
Debug.Print line
' Output:
' Filenam...
See Also
- cConsole class – the console, and the full-screen mode that owns a buffer
- Types – ConsoleColor, ConsoleCharInfo and ConsoleRect
- cProgressBar, cDataGrid and the other components, which draw into a buffer