It's sometimes useful to be able to relay information back to the user with line by line text. The need is to provide a listing of information forming a trace. An example most developers will be familiar with is the Visual Studio 'output view pane' during build, load, 'find results', etc.
A common approach to building such a control is to modify one of the common controls, but I felt it would be much more convenient to have something ready to use and built for the task, so I decided to fill the gap in my toolbox and write one based on
UserControl. It's a fundamental interface device that predates the GUI control and it continues to be an interesting and enjoyable development task.
This first major revision to the control, which principally adds support for cut and paste, also includes an improved demonstration program and a background event sink helper for building a more robust and responsive console host. This might be of interest to those building server consoles because this type of program can be running for considerable periods of time and confidence in a controls ability to manage resources, especially one that might be involved in a lot of activity, is important. Refer to the Sample Application section for more details.
Future plans are to include persistence, with save log to file and automatic truncate log to file.
Those interested in control development might find some of the control code useful. In this respect, I have demonstrated using AutoScroll combined with painting text, etc. The code may be helpful to those learning to use the AutoScroll feature. It also demonstrates an implementation of Cut and Paste functionality. Those wanting to manipulate strings via mouse or keyboard will hopefully find the source code useful.
Creating and Using the ConsoleWriter Control
To incorporate the control into your C# Forms Project, add the file ConsoleWriter.cs to your project. You can refer to
Elements.ConsoleWriter or add
using Elements; at the top of your code file to be able to refer to just the class.
To code your own
ConsoleWriter member, you can do something like:
private ConsoleWriter m_ConsoleWriter;
public ConsoleWriter MyConsoleWriter
m_ConsoleWriter = value;
public class MyForm()
MyConsoleWriter = new ConsoleWriter();
MyConsoleWriter.Dock = DockStyle.Fill;
If you use form designers, you should find the
ConsoleWriter available in the Toolbox once it is added to your project. You can drag it onto your design surface from there.
The control derives from
UserControl so the inherited functionality is applicable to the
ConsoleWriter where relevant.
There are four methods exposed by the
Add(string text): This takes a
string parameter and is the text you want to output to the
The principle behind this one means of adding output to the control is that the user can either send in one line of text, or text that contains multiple lines. The
Add function parses the input text for line feeds and processes each of those lines as a new row. There are many ways to prepare
strings as multiple lines with .NET. One of the most useful is
System.Text. This allows you to build up a
string and includes a method
AppendLine which will embed line feeds in the
string output available from its
Clear(): This is a parameterless method that instructs
ConsoleWriter to clear the console.
CopySelectedToClipboard(): This is a parameterless method that instructs
ConsoleWriter to place selected text on the system clipboard.
SelectAll(): This is a parameterless method that instructs
ConsoleWriter to select all of the text in the control.
There are three properties exposed by the
LineBufferLimit: In order to be able to scroll back through previous output, the control retains a buffer of lines. You can set this to a positive integer value to suit your needs. The default value is 1000. If you set it to zero or below, then the buffer will grow unchecked. A call to
Clear() will empty the buffer. The buffer is a
SortedDictionary that holds all the information the control needs to paint the text from the current scroll position.
AutoRemoveCount: If there is a
LineBufferLimit greater than zero, e.g. the default of 1000, when that limit is reached the value of
AutoRemoveCount - the default is 250 - items are removed from the top of the buffer. Therefore in the default mode (1000/250), once the
LineBufferLimit has been reached there are always between 750 and 1000 items in the buffer. If you have a
LineBufferLimit greater than zero but an
AutoRemoveCount of zero, then the buffer will behave like a FIFO queue with a fixed size once the limit is reached. This is not advised unless the effect is desired as the list can never enter a Stationary mode. (See the section headed Trailing for more about modes.)
ConsoleWriter supports alternating colored lines, which appear as bands like listing paper if the chosen color is pale enough. The default color is
SystemColors.ControlLight. The background color defaults to
SystemColors.Window and the text color defaults to
If you want the optimization of no
BandColor, e.g. a plain background, then set the
BandColor to the
BackColor value - those interested in the control code will note that the
OnPaintBackground override of ConsoleWriter.cs checks for this equality and makes no attempt to paint bands of the same color.
Set an alternative
BandColor value if required. This code example sets the control to a plain display:
MyConsoleWriter.BandColor = ConsoleWriter.BackColor;
Derived Color Properties
- Use the
BackColor property to set an alternative background color, and similarly set the
ForeColor derived property to change the color of text.
ConsoleWriter.BackColor = System.Drawing.Color.White;
ConsoleWriter.BandColor = System.Drawing.Color.MintCream;
ConsoleWriter.ForeColor = System.Drawing.Color.Black;
ConsoleWriter Font defaults to Courier New 8.25 a fixed character width font which provides a standard appearance and easily read output. You can choose any font for the control by setting the
Font property at construction, e.g. for designer hosted composition, these properties are available in the properties task pane of Visual Studio.
MyConsoleWriter.Font = new System.Drawing.Font("Arial", 11.25f,
ConsoleWriter has two modes, either Stationary or Trailing. If Trailing the control keeps the latest output visible at the bottom of the screen, this means that the window is being scrolled automatically and the older lines beyond the display capacity of the
ClientRectangle Height will scroll off screen. Those interested in how this is achieved using the functions related to AutoScroll can see this in the Controls
InternalRefresh method. The
AutoScrollMinSize property is checked and, if necessary, reset, and if the control is in Trailing mode the
AutoScrollPosition is set to correspond with the last line, e.g. the most recent.
When Stationary, the control is at a fixed point in the AutoScroll
DisplayRectangle - this allows the user of the program to scroll back through the current buffer of lines.
These features are reasonably intuitive to the control user. The control starts life in Trailing mode and will therefore show latest output and scroll automatically. If the user scrolls the display up at all, then the control will automatically enter the Stationary mode.
When the control is scrolled back down to the end of the list, then the Trailing mode will automatically be engaged again. Pressing the keys Ctrl+End will also re-engage the trailing end of the list.
Command Keys and Mouse Control
The control responds in a natural manner to scrolling using the mouse and scrollbar controls. The scroll arrows when pressed move the display by a line increment either up or down. Direct manipulation of the scroll box (thumb) moves the display with the standard scroll behaviour as does mouse clicking within the scrollbar shaft itself. The control also responds normally to mousewheel activity.
Text can be selected by holding the Mouse Left Button down where selection should start and moving to determine the extent of the selection. Releasing the mouse button ends the selection. The text remains selected until another mouse click is received.
Control developers can observe the
OnMouseWheel overrides to see how this is implemented. You will note that the
VerticalScroll.SmallChange is set to the
LineHeight to ensure that the one line increment can be achieved.
When the mousewheel attempts to move the display beyond the end of the list, the control will automatically enter the Trailing mode. This is also the case for the scroll arrow. I have not implemented this behaviour when the thumb is moved to the end of the list because this allows the user to remain in Stationary mode when observing values at the end of the list. This is most appreciable when the control is rapidly receiving values.
The control also responds to the following command keys:
- Ctrl-Home scrolls the display to top of the
DisplayRectangle and leaves the control in Stationary mode.
- Ctrl-End scrolls the display to the end of the
DisplayRectangle and leaves the control in Trailing mode (A single click to the thumb will halt Trailing and enter Stationary mode).
- PageUp and PageDown scroll the display by the display height. If the PageDown key attempts to move beyond the end of the list, the control will automatically enter the Trailing mode.
- ArrowUp and ArrowDown scroll the display by the line height. If the ArrowDown key attempts to move beyond the end of the list, the control will automatically enter the Trailing mode.
- Ctrl-A Selects all the text in the control.
- Ctrl-C Copies any selected text to the
Control developers can observe the
ProcessCmdKey override to see how this is implemented.
Sample Application - ConsoleWriterSample.exe
To help you evaluate and understand how the
ConsoleWriter works and behaves, I have created a sample MDI Application. Its sole purpose is to demonstrate one or more
To keep a constant supply of information available to the
ConsoleWriter, the main form of the application has a timer. The child windows of this application all contain a
ConsoleWriter and they can subscribe to a timer event published by the main form. When the main form receives the timers elapsed event, it broadcasts this event to the subscribing child windows and they in turn write this information to the
ConsoleWriter. To provide further test output to the
ConsoleWriter the child form also supports two commands. One is to list the current environment and the other to list the currently loaded modules.
You can alter the interval period of the main form timer via a dialog provided under the main forms options menu. You can alter the settings for the
ConsoleWriter control programmatically in the
ConsoleChild Create method.
In the original release of this control, I had placed the
DoEvents method in the
Add method with the intention of freeing up the UI when several
ConsoleWriters were under high usage. This is because I found that whilst using the Sample Application with several
ConsoleWriters all receiving messages at an intense rate, for example 100 millisecond intervals, the UI could be frozen or choked. On reflection
DoEvents was not a good idea, as it can cause problems in the hosting program and I think it therefore undermines the integrity of a control. It didn't solve a bigger problem I encountered either - when disconnecting and disposing from a high speed event source. I got exceptions for attempted invocations on disposed objects, despite testing for Disposed and Disposing, and making several attempts to find a robust technique.
I eventually found the solution to both problems in a class I had developed a while back to host a thread in a convenient way. I used this thread hosting class to be an event sink for the
Form1 timer event and an event source for the
ConsoleWriter. This technique has cured both the UI problem and the event detachment problem. In my testing, I have not experienced any problems above 100 milliseconds (my testing uses 9 open
ConsoleWriters receiving at this rate). The next section describes the
Thread Class and how an application can use it to achieve a more robust
The bitmaps used in the menus and buttons are public domain and available from www.famfamfam.com.
EventWaiter Thread Helper Class
EventWaiter is a base helper class from which you can inherit and write your own threadstart thread classes. Some might find this useful as a general purpose threading utility class.
EventWaiter class is a completely optional class you can use to achieve a more robust console hosting environment when programming for extreme conditions, or defence, or when you think hosting an event sink on another thread would help your program.
It is particularly of use if, for example, there are going to be many consoles and they are going to be placed under high and sustained loads. Under such conditions, the
EventWaiter class can help keep the UI thread available for user input and intervention, which is important with these type of programs. For instance a console might need to be shut down, but if the UI is not responding to allow the command to detach to be issued - then the program wouldn't be working properly or likely not responding at all.
EventWaiter class provides you with a thread hosted sink for your external event source(s) and in turn an internal event source for your
ConsoleWriter. This thread can be safely shut down allowing you to detach from the source before say, disposing the
ConsoleWriter or attaching a new event source.
The example that follows explains the Sample Application usage of
As explained in the Sample Application notes, each child MDI window (
ConsoleChild), that hosts a
ConsoleWriter, can subscribe to the timer event published by
Form1. Instead of subscribing directly in the UI thread of our application, we use a class derived from the
EventWaiter class to receive these events on a separate background thread.
In ConsoleChild.cs the event
OnMenuItemSubscribeClick receives user menu instruction to start receiving events.
ConsoleChild has a member of type
Form1EventWaiter and this is a thread event handler class derived from
null, then a subscription is created. The
ConsoleChild helper method
CreateForm1EventWaiter() is called to handle creating the thread event handler and starting its execution. In
Thread is created with thread start parameter of type
EventWaiter.EventWaiterThreadStart. This is the method that is executed when the thread starts life.
In this example, the thread start parameter
TypeString is set to "
EventWaiter derived class), the parameter
EventWaiterEventHandler is set to the
ConsoleChild handler -
OnEventWaiterEvent, and the
Tag parameter to
ConsoleChilds MdiParent, which of course is
Form1 - that provides the event
ConsoleChild is to subscribe to.
OnMenuItemSubscribeClick Form1EventWaiter is not
null, then a subscription to the event exists and therefore
UnSubscribe is called. In
Form1EventWaiter.DetachEventWaiterEvent is called with the
OnEventWaiterEvent handler to detach.
Now take a look at
OnEventWaiterEvent. The first obvious point is that the code in the handler does not deal directly with the event. That's because our event arrives on a thread that's not the main UI thread. The event needs marshalling to the UI thread. This is done by 'Invoking' a method on the UI thread using a 'delegate' which constitutes an invocation signature. In this case
HandleEventWaiterEvent is called with the event arguments.
HandleEventWaiterEvent is important, not just to handle the subscribed event on
Form1, but to also handle some of the requirements in running the new thread.
eventWaiterEventArgs.Message is switched for its
string value. The thread class sends the message "
FirstRun" after initialization. The thread is ready to start so
ConsoleWriter casts the sender object parameter to the
Form1EventWaiter member and calls
Form1EventWaiter.Go(); to start the events.
UnSubscribe is called, the event is detached and the thread class message "
Detached" is received in
HandleEventWaiterEvent. This means that
ConsoleWriter can now safely call
The thread class message "
Form1Event" was defined in the derived
Form1EventWaiter class and is the
Form1Event that has been subscribed to arriving. In this case,
ConsoleWriter adds the message to its
- Version 1.0.0 Released: 8th January, 2009
- Version 1.0.1 Released: 17th January, 2009 - Fixes to
OnResize override code that could cause a refresh problem
- Version 2.0.0 Released: 11th April, 2010 - Adds cut and paste functionality and background event sink helper