Virtual Developer Workshop: Containerized Development with Docker
Although computer users often view console applications as relics, they are still very much a part of the system administrator's and the power user's toolboxes. They are extremely useful when you need to script highly repetitive, and possibly complex, operations.
Earlier of versions of Visual Basic did not offer much help in creating console apps. VB 6, and earlier versions, could create console apps, but it was not a simple or straightforward process. Creating a console app in VB 6 required making Win32 API calls. Even then, you could not control the cursor position or the color of the text. You were pretty much restricted to writing to the console from left to right and top to bottom.
VB .NET introduced new console app features. It provided not only a console application project in the New Project... dialog, but most importantly it provided a Console object. You no longer had to mess around with the Win32 API. However, you were still mostly restricted to writing to the console left to right and top to bottom. VB .NET lacked the methods or properties for changing the position or color of the cursor or text, as well as additional more advanced features.
New Console App Features in VB 2005
Building on the Console object in those earlier VB .NET versions, VB 2005 added many great features for console applications. The following are just a few of the more exciting capabilities of VB 2005:
- Clear the console window
- Get and set cursor position and size
- Get and set window height and width
- Get and set foreground and background color
- Select and move sections of text within the console window
The rest of this article steps through the creation of a console application that demonstrates the use—and usefulness—of these features.
Example Application: DirCopy
I often copy files from one directory to another from a console window. When I'm copying a directory with a large number of files, I often wish that I had some indication as to how close to completion the copy is. Is it nearly done, or should I go get a cup of coffee? To answer that question, I built a console app that displays a progress bar that graphically shows, within the console window, how close the copy is to completion.
Defining the Functionality
To keep things simple for this demonstration, DirCopy will accept two command-line arguments: a source directory and a destination directory. It then will copy all of the files from the source to the destination. It will NOT copy any subdirectories from the source to the destination.
To start, I wrote the DisplayUsage subroutine. If DirCopy is invoked with too many or too few command-line arguments, it will call DisplayUsage and then end. Figure 1 shows the results of running DirCopy without any command-line arguments.
Figure 1: Result of DisplayUsage Subroutine
The following is the code for the DisplayUsage sub:
Private Sub DisplayUsage() Dim originalForegroundColor As ConsoleColor = Console.ForegroundColor Console.Clear() Console.ForegroundColor = ConsoleColor.Green Console.WriteLine("DirCopy 1.0") Console.WriteLine("Written by Josh Fitzgerald") Console.WriteLine(New String("-", Console.WindowWidth)) Console.WriteLine("DirCopy will copy all of the files from the source folder to the") Console.WriteLine("destination folder. While the files are copying, a progress bar") Console.WriteLine("will display the percent complete.") Console.WriteLine() Console.WriteLine("If a directory name contains spaces, enclose it in double quotes.") Console.WriteLine() Console.Write("Example : ") Console.ForegroundColor = ConsoleColor.Magenta Console.WriteLine("DirCopy C:\MyFolder C:\MyNewFolder") Console.ForegroundColor = ConsoleColor.Green Console.WriteLine() Console.Write("Example : ") Console.ForegroundColor = ConsoleColor.Magenta Console.WriteLine("DirCopy ""C:\My Folder"" ""C:\My New Folder""") Console.ForegroundColor = originalForegroundColor End Sub
The first thing the subroutine does is create a variable named originalForegroundColor, and then it stores the value of Console.ForegroundColor. At the end of the sub, the code sets the Console.ForegroundColor property back to originalForegroundColor. As you explore the code, you'll notice that it does this in every subroutine that modifies the foreground or background colors. I wanted to make sure that DirCopy always leaves the console with the same colors that were in effect before DirCopy ran.
The next thing DisplayUsage does is clear the console screen by using the Console.Clear method. This is one of the new methods in VB 2005, and it makes it very easy to ensure you have a clear console window.
I wanted to make the help text stand out a little bit, so I set the ForegroundColor property to green. Then, I wrote several lines of text to the screen using the Writeline and Write methods that describe the DirCopy application. At end of the description, I included a couple of examples of how to run DirCopy. I wanted my examples to stand out from the rest of the text, so I set the ForegroundColor to magenta.
The Engine: CopyFiles
The CopyFiles subroutine does most of the work in this application. It is responsible for getting the list of files from the source directory and copying them to the destination directory. It also creates a ConsoleProgressBar object and manages the progress bar:
Private Sub CopyFiles(ByVal srcDir As String, ByVal destDir As String) Const BufferSourceTopLine As Integer = 8 Const BufferDestinationTopLine As Integer = 7 Dim rowIndex As Integer = 7 Dim originalForegroundColor As ConsoleColor = Console.ForegroundColor Console.CursorVisible = False Console.Clear() Dim numberOfFiles As Integer numberOfFiles = My.Computer.FileSystem.GetFiles(srcDir).Count Dim pb As New ConsoleProgressBar(numberOfFiles) DisplayHeader(srcDir, destDir) Dim fileCounter As Integer = 1 For Each f As String In My.Computer.FileSystem.GetFiles(srcDir) Dim fi As New System.IO.FileInfo(f) Console.ForegroundColor = ConsoleColor.Green Console.SetCursorPosition(0, rowIndex) Console.Write(fi.Name) If rowIndex < Console.WindowHeight - 1 Then rowIndex += 1 Else Console.MoveBufferArea(0,_ BufferSourceTopLine, _ Console.WindowWidth, _ Console.WindowHeight - _ BufferSourceTopLine, _ 0, _ BufferDestinationTopLine) End If My.Computer.FileSystem.CopyFile(fi.FullName, destDir &"\" & fi.Name) pb.Update( fileCounter) fileCounter += 1 Next> Console.ForegroundColor = originalForegroundColor Console.SetCursorPosition(0, Console.WindowHeight - 1) Console.CursorVisible = True End Sub
Once again, the first thing the code does is save the current ForegroundColor. It then uses another new feature and sets the CursorVisible property to False. After clearing the console window, it retrieves the number of files in the source directory and uses that number as the maximum value for the ConsoleProgressBar constructor. (I'll delve into the details of the ConsoleProgressBar later.)
I called the DisplayHeader subroutine to print some info about the copy operation to the console window. I won't go into detail on it because it is functionally very similar to the DisplayUsage subroutine.
I used a For...Each loop to loop through all of the files in the source directory. I used a rowIndex variable call to keep track of which row in the console to print the file name to. As the loop progresses, rowIndex gets incremented by one each time until it reaches the bottom of the console window. Once I've reached the bottom of the console window, I utilize another new console app feature, the MoveBufferArea method (more about that in the next section).
After updating the display and copying the file, I updated the progress bar by calling the "Update" method of the ConsoleProgressBar class.
Once the loop completed and files were copied, I set the ForegroundColor back to its original color, set the cursor position on the bottom line of the console window, and made the cursor visible again.
Moving Sections of the Console
The MoveBufferArea method allows me to specify a section, or buffer, within in the console window and then specify a set of coordinates to move it to. I wanted the list of file names to appear as a scrolling list, while maintaining the header info and progress bar at the top of the console window. Once I've printed a file name to the last line in the console, I stop updating the rowIndex variable and start using the MoveBufferArea method.
MoveBufferArea takes six parameters:
- The first parameter specifies the left edge of the area you want to select.
- The second parameter specifies the top line of your buffer area.
- The third and fourth parameters specify the width and height of your buffer area.
- The last two parameters specify the left edge and the top line of the area you want to move your buffer to.
Console.MoveBufferArea(0, _ BufferSourceTopLine, _ Console.WindowWidth, _ Console.WindowHeight - BufferSourceTopLine, _ 0, _ BufferDestinationTopLine)
This code tells the MoveBufferArea method to start at column 0 and line BufferSourceTopLine, which is a constant defined as 8. Then, it sets the buffer area width equal to the width of the console window and sets the buffer area height equal to the difference between the console window height and the top line of the list of file names, BufferSourceTopLine. The last two parameters specify the destination location of my buffer area as column 0 and the constant BufferDestinationTopLine, which is defined as 7.
In simpler terms, the code selects the second file name in the list through the last name and moves that buffer area up one line, effectively removing the top file name and making room at the bottom for the next file name.