ow difficult do you think it would be to create a SQL tool like Query Analyzer or SqlServer Management Studio? How about a programming and execution framework for VBScript? Or how about a dual code-and-WYSIWYG editor for HTML?or a development IDE for creating and testing XSLT transformations? And why would you bother programming your own tool when there are many fine commercial products in each of these categories?
It turns out there are several reasons. For example, you may want to incorporate such a tool as an integral component of your product. Or perhaps you just have a favorite tool that you find mostly adequate, but that you’d prefer to be able to customize to your own specifications. In this article, I’ll show you tools and techniques that will facilitate building such applications.
It’s important to realize that all the tools I’ve mentioned so far use a common model?the classic input-process-output model illustrated in Figure 1.
However, this high-level system description is applicable to a much broader range of applications than the ones already listed, and?indeed?in many systems besides software. Figure 2 shows a more refined model that fits the types of tools under consideration more closely. That is, all these tools fundamentally provide an interactive editor control for input and a textual, tabular, or graphical control for output. I submit that any of the four tools I mentioned could essentially make use of the same type of interactive editor control. Consider Visual Studio’s own built-in editor, which performs keyword highlighting, automatic block indentation, word completion (Intellisense), and much more, for a variety of languages. I will give you ideas on how to start creating your own such editor here. I am not going to cover everything that the Visual Studio editor can do, but I shall provide a complete component solution for keyword highlighting for an arbitrary language of your choice, block commenting and indenting, external compilation and execution, and other useful features. Then, I’ll walk you through the process of incorporating that component into an elementary integrated development environment (IDE) using two different application types: a SQL query tool and a VBScript execution framework.
|
|
Application One: An IDE for SqlServer
The code download that accompanies this article includes source code for a demo SyntaxHilightAndEditDemo project that you’ll build during this article. The project uses an external library. The necessary library components are already included with the demo project but the source for those components is not; however, you can download the source code for the library components from my open source web site.
One of the library components is a control called a SyntaxHilightTextBox, based on a RichTextBox. The SyntaxHilightTextbox is my core user editor control. You’ll see how to use it in the first walkthrough.
To begin, create a new Visual Studio project using the Windows Application template. You’ll need to add some controls to your toolbox. Right click on the toolbox in the visual designer and select “Choose Items?” That brings up the dialog shown in Figure 3. The checked items are already added to your toolbox.
The component you’re looking for won’t appear in the list, though, so click the Browse button, and then navigate to the CleanCodeControls.dll file ( see Figure 4) that you’ll find in the downloadable code accompanying this article. Select the file, and then click the Open button.
|
|
When you click the Open button, Visual Studio adds the controls from the CleanCodeControls library to the list of available items and automatically checks them (see Figure 5).
|
|
Click OK to close the dialog and update your toolbox. You’ll find the CleanCode controls at the bottom of the toolbox, including two subclasses of the SyntaxHilightTextBox, one for SqlServer and one for Oracle (see Figure 6).
Adding the Controls
You will learn more about the details of the control a bit later. For now, drag a SqlServerSyntaxHilightTextBox onto your form, add a DataGridView for showing SQL output, and a standard Button control as well, to complete the basic form (see Figure 7). The demo project also includes a SplitContainer so users can adjust the relative sizes of the two panes at runtime.
|
|
Customize Input Pane Properties
Select the SqlServerSyntaxHilightTextBox and then scroll through the Properties pane.
Tip: Select the sort-by-category button on the Properties pane (the leftmost button) rather than the sort-alphabetically button (the second button). That way, you will see all the custom properties for this control in one miscellaneous category (see Figure 8). |
If the AutoHilight property is set to False, change it to True. That property controls whether the control performs syntax highlighting automatically (True) or manually (False).
Generate a Connection String for Your Database
In Visual Studio’s Data Sources pane, add a connection to the database of your choice. I include a small database with this demo application (called simpleDB) for your convenience. Adding the connection creates a DataSet in the Data Sources pane (see Figure 9).
|
|
Adding the connection also generates a new settings file and inserts a specific connection setting (see Figure 10). The database is then accessible from code.
Connecting the Input to the Output
Now that you have a connection string accessible from your code, you simply need to connect the input to the output. Here’s the entire program:
namespace SyntaxHilightAndEditDemo { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void executeButton_Click(object sender, EventArgs e) { DataTable dataTable = new DataTable(); SqlDataAdapter tableAdapter = new SqlDataAdapter( hilightTextBox.SelectedOrAllText, Properties.Settings.Default.simpleDBConnectionString); try { tableAdapter.Fill(dataTable); } catch (SqlException ex) { MessageBox.Show( ex.Server+": "+ex.Message); } dataGridView1.DataSource = dataTable; } } }
![]() | ? |
Figure 11. Running the Sample Program: A trial run of the finished program shows that the application correctly highlights both SQL keywords and comments. |
Build and execute the program. Enter a simple query such as that shown in Figure 11. Notice that as you type, the control highlights the SQL keywords in blue (unless you changed the KeywordColor property in the designer). Notice also the comment turns green as soon as you close it. Press the Execute button and you’ll see the results appear in the bottom pane.
My recent article “Exploring Secrets of the .NET DataGridView” examines the code in this application’s Execute event handler in great detail, plus quite a bit more, so I won’t cover it further here. I encourage you to review that article for further techniques for working with a dynamic DataGridView.
Get Under the SyntaxHilightTextBox’s Covers
You can leverage a lot more power from the SyntaxHilightTextBox. First, consider the base class itself, the RichTextBox. I am sure that Microsoft has included details on the user interface somewhere in the RichTextBox documentation, but I have yet to find it. What I have gleaned from experimentation is that the RichTextBox control (and probably other Form controls) use most or all of the keystrokes common in most Windows applications. Table 1 lists these keystrokes and how the control reacts to them:
Keys | Description |
Control-A | Select all. |
Control-C | Copies the selected text to the copy buffer. |
Control-V | Pastes text from the copy buffer to the cursor position. |
Control-X | Cuts the selected text, adding it to the copy buffer. |
Enter | Advances to the next line in the control. |
Tab | Advances the focus to the next control (that is, this control loses focus). |
Control-Z | Undo. |
Home | Move to the beginning of the line. |
End | Move to the end of the line. |
Ctrl-Home | Move to the beginning of the text. |
Ctrl-End | Move to the end of the text. |
Ctrl-PageUp | Move to the beginning of the window. |
Ctrl-PageDn | Move to the end of the window. |
PageUp | Scroll the window up one page. |
PageDn | Scroll the window down one page. |
RightArrow | Move forward one character. |
LeftArrow | Move backward one character. |
Ctrl-RightArrow | Move forward one word. |
Ctrl-LeftArrow | Move backward one word. |
Single click | Position the cursor at the mouse position. |
Double click | Select the word under the mouse pointer. |
Triple click | Select the line under the mouse pointer. |
Shift-xxx | Shift in conjunction with movement keys extends the current selection. |
![]() | ? |
Figure 12. Underlying RichTextBox Capabilities: The base class handles rich text, so stylized text retains its styling when pasted. The control’s syntax-highlighting feature adds the color to pasted text automatically. |
Note that the control does not operate on plain text, but on rich text. That means you could create text with a different size and different font?in Word, for example?and then paste it into the control (see Figure 12), without losing the font and size settings. The control does not expose specific keystrokes to manipulate fonts, but it exposes properties that allow you to do so after you instrument your code appropriately. Also, with suitable code, you could load an RTF file into the control.
You can use the control to create a basic word processor, then, with little coding. How fancy you make it is up to you. The SyntaxHilightTextBox adds some additional built-in features and keystrokes to make it even easier for you to make a powerful user interface. Table 2 lists the additional keystrokes; I’ll provide details on the operations in the table a little later in this article.
Keys | Description |
Ctrl-Shift-C | Comment the current selection. |
Ctrl-Shift-U | Uncomment the current selection. |
Alt-> | Shift the current selection to the right by adding a tab to the start of each line. |
Alt-< | Shift the current selection to the left by removing a tab (or spaces) at the start of each line. |
Ctrl-Shift-H | Reapply syntax highlighting. |
Ctrl-Enter | Insert a tab character (or tab-equivalent spaces). |
Control-> | Increase the font size of the current selection. |
Control-< | Decrease the font size of the current selection. |
Exposed Properties
In addition to supplementing the keystrokes the control handles, the SyntaxHilightTextBox also exposes properties that let you customize the control’s behavior (see Figure 8). Here’s a description of each public property.
- AutoHilight. This Boolean property determines whether the control applies highlighting as users type. Because users are free to arbitrarily change content anywhere in the text box, either by typing or by cutting and pasting, I chose to re-highlight everything on any change. That might sound like a lot of overhead, but it executes nearly instantaneously. Instant highlighting has one drawback though, which is why this property is exposed to allow you to turn it on or off: The control adds highlighting to the queue of processed items just like any other overt user action, meaning that it includes every highlighting action in the list of things you can undo with the undo command (control-Z). Practically speaking, that makes the undo command almost useless when AutoHilight is turned on. You may want to give users a button or menu toggle to let them decide whether to use it. The default value of the Autohilight property is false.
- RefreshDelay. This property is closely related to the AutoHilight property. The RefreshDelay property specifies the time to wait after the user pauses typing before invoking the automatic re-highlighting operation (if enabled). This value is specified in milliseconds. The default value is 500 milliseconds.
- ClosedCommentStartMark, ClosedCommentEndMark, and EndOfLineCommentMark. These three properties are closely related. The first two work together to define what I call a closed comment. The similar EndOfLineCommentMark property defines the other type of comment, an end-of-line comment. Closed comments require a starting mark and an ending mark, and may span multiple lines. End-of-line comments require only a starting mark and terminate at the end of the line. The C# code in Figure 13 illustrates both types of comments. Note that code may continue after a closed comment, whether the comment occupies all or a portion of one or multiple lines. For an end-of-line comment, there is no closing character; the end-of line provides an implicit closing character. The defaults are /* and */ for closed comments, and // for end-of-line comments.
- QuoteChars. This string property lets you specify zero, one, or multiple characters that may delineate a string constant?one of the element types highlighted by the control. A matching pair of any specified character delineates a string; the user may not mix different characters to delineate a single string. The property defaults include both single and double quote characters.
- KeywordColor, CommentColor, and QuotedColor. These three properties specify the colors of the three highlighted element types: language keywords, comments, and quoted string constants. The default colors are blue, green, and fuchsia, respectively.
- ExpandTab. This Boolean property specifies whether to insert spaces (true) or tabs (false) during tab-related operations. There are two such operations available: the Ctrl-Enter key combination adds a tab (or the equivalent number of spaces), and right-shifting a text block adds either tabs or spaces. By default (false), the control adds tabs.
- TabSize. Closely allied with the ExpandTab property, TabSize specifies the number of spaces to equate to a tab when ExpandTab is true. Conventionally, you may be used to a tab occupying eight spaces, but values of four and two are quite common as well. The default is four.
- SelectedOrAllText. You can programmatically obtain all the text in a standard RichTextBox via the Text property, or a user-selected subset via the SelectedText property. But in a query browser or a script development tool, users are likely to want to operate on the entire contents of the container at some times, or just the selected portion at other times. A reasonable course of action, then, is to assume that if some text is selected, act on that; if not, then use all the text. The SyntaxHilightTextBox provides the aptly named SelectedOrAllText property that returns precisely that. You can look at the query browser sample program to see how it uses this property. There is no default for this property.
![]() | ? |
Figure 13. Two Comment Types: Both end-of-line comments (marked with “//”) and closed comments (marked with “/*” and “*/”) are illustrated here. |
Control Operations
Now that you are aware of the SyntaxHilightTextBox’s public properties it is easier to discuss control operations. I should point out that the very name of the control is not altogether apt; syntax highlighting is mainly a background operation. The remaining operations are more like editing aids, and therefore more in the foreground. But the syntax-highlighting aspect is the more unique and more powerful feature, which is why I chose to name it as I did.
Shifting Blocks of Text
Properly formatted code?be it SQL, Perl, XML, or any other language?relies on visual cues from indentation to facilitate understanding by human readers. The SyntaxHilightTextBox provides two keystrokes to assist with indentation.
- Alt-> shifts code to the right, increasing the indentation.
- Alt-< left shifts code left, decreasing the indentation.
![]() | ? |
Figure 14. Quick Indentation: The first row of images shows the effect of selecting a block of text and right-shifting it. The second row selects successively larger, encompassing rows to show nested right-shifting. |
Figure 14 illustrates the utility of this feature. To test it, enter the seven lines of text as shown at the upper left of the figure; the control colors the keywords and the string constant. Advance to the second frame in the top row and mirror the selection you see by selecting the three rows containing the inner select statement. Press Alt-> and the text shifts to the right, as shown in the third frame. The text remains selected so you can continue to operate on it if you wish. Try another Alt-> to shift it even further right, and then bring it back again with Alt-<. Select all but the first row, as shown at the bottom left. Apply another right-shift and you end up with the final frame shown in Figure 14.
Recall the ExpandTab property discussed earlier. In this demo program it is false, so genuine tabs are used. To confirm this, click at the front of the second “select” keyword, then use the left arrow key to move to the left. A single press of the key should move your cursor below the “where” keyword. If the program was using spaces, you would have moved only a single character width.
The underlying RichTextBox control does not expose a mechanism for changing the width of a tab. But if you switch to tab-equivalent spaces, then you do. Terminate the program, go to the visual designer, select the SyntaxHilightTextBox and find the ExpandTab property. Change it to true, and change the TabSize property to 2. Re-execute the program and repeat the test process described earlier and you get much less dramatic movements when you right shift.
Left-shifting has a subtle difference from right-shifting. Right-shifting will always add either a tab or tab-equivalent spaces, dependent solely on the ExpandTab property. Left-shifting, however, ignores whether spaces or tabs were inserted; the object of the operation is to remove them, whatever they are! Left-shifting removes white space from the beginning of the line. So, if the first character of a given line is a tab, the operation removes that tab; otherwise, it removes the number of spaces specified in the TabSize property, if present. If neither condition is met, then no action is performed.
Both right- and left-shifting operate on either the current line (when there’s no selection) or the lines comprising the current selection. Neither operation requires users to select full lines.
Commenting Blocks of Text
Commenting and uncommenting are conceptually similar to shifting, although the operation inserts or removes comment characters rather than spaces or tabs. Users are free to manually insert either closed comments or end-of-line comments, but the commenting invoked by keystroke commands acts exclusively on end-of-line comments. Closed comments introduce dependencies between lines; a middle line of a three-line comment, for example, is only a comment because line one contains an opening mark and line three contains a closing mark. End-of-line comments allow every line to remain independent so users may then freely comment or uncomment any arbitrary selection of lines without changing semantics of its neighbors.
![]() | ? |
Figure 15. Keystroke Commenting: The sequence illustrates successive applications of block commenting, with the end-of-line comment character defined as a double hyphen (–). |
Figure 15 shows a sequence of commenting operations. ExpandTabs is set to true for variety (note the smaller indentations in this figure as compared to Figure 14). In the first frame the cursor, though not visible, is at the beginning of the line containing “two”. Pressing the comment keystroke (Control-Shift-C) adds a comment mark to the beginning of that line, as shown in the second frame. In the third frame I changed the selection to encompass two lines, and the fourth frame shows the result of commenting them. The fifth frame is the final result after changing the selection again and performing the commenting operation. As an added exercise, leave the selection where it is and uncomment the selection repeatedly (Control-Shift-U). The control does what you expect?it removes comment markers if present while ignoring lines without comment markers.
Sizing Blocks of Text
A common feature in word processors?but not often in code editors?is the ability to change the size of a block of text. The SyntaxHilightTextBox provides this ability, using the standard Ctrl-> and Ctrl-< keystrokes to increase and decrease font size, respectively. Rather than make these keys change the font size by a constant amount, I have programmed them to adjust the increment relative to the current font size. So, for example if you start at 8-point type, the keys will adjust font size by a single point. But if you are at 36-point type, the font size will change by 4 points. Imagine if you were at 64-point type and you wanted to go to 24-point, but had to do it in 1-point increments!
Application Two: A VBScript IDE
Now that you have a good overview of how to use the finished control, this section discusses how to work with and customize it in development.
Near the beginning of the article you dragged a subclassed control instance?a SqlServerSyntaxHilightTextBox?onto your form and worked with that. In fact, you only had two subclasses that you could pick from, no parent or main class. The main class?SyntaxHilightTextBox?is an abstract class; you must subclass it to create an instantiable control. See the SyntaxHilightTextBox API here. Here is the entire program for one such subclass:
public partial class SqlServerSyntaxHilightTextBox : SyntaxHilightTextBox { public SqlServerSyntaxHilightTextBox() { InitializeComponent(); EndOfLineCommentMark = "--"; } protected override void DefineWordList() { WordList = new string[] { "ABSOLUTE", "ACTION", "ADA", "ADD", . . . "WRITE", "YEAR", "ZONE" }; } }
This class does only two things:
- The constructor changes the EndOfLineCommentMark to the appropriate string for SQL. All other property defaults are adequate for T-SQL, the language that SqlServer uses. You might argue that the QuoteChars property should be set to a single quote rather than the default of both single and double quotes. I chose not to do this because while T-SQL doesn’t normally use double-quotes for string constants, it can use them in limited situations to delimit strings. Alternatively, you might choose to add square brackets to the list as well (e.g. to create bracket-delimited names such as [dbo].[Table Name]).
- The DefineWordList method specifies a value for the WordList. This WordList is, of course, the key to syntax highlighting. All you have to do is provide a list of keywords for the language of your choice and those keywords will highlight appropriately when you use the control in an application.
The rest of this article walks you through the process of creating a SyntaxHilightTextBox subclass for editing the VBScript language.
Step 1: Generate the Project
Add a new project to your solution (or just start a new solution). The new project should use the Windows Control Library template. The visual designer will display an empty control (see Figure 16).
|
|
Step 2: Name Your Control
Rename the UserControl1.cs file to VBScriptSyntaxHilightTextBox.cs. Visual Studio will recognize what you are doing and try to do the right thing (see Figure 17).
Select “Yes” to keep your file in sync with your class.
Step 3: Reference the Parent Class
Add a reference (right-click on References in the Solution Explorer) to the CleanCodeControls library so you will have access to the base class.
Step 4: Inherit from the Parent Class
Open the code-behind file VBScriptSyntaxHilightTextBox.cs. Change the class inheritance from?
public partial class VBScriptSyntaxHilightTextBox : UserControl
to ?
public partial class VBScriptSyntaxHilightTextBox : SyntaxHilightTextBox
Visual Studio will again provide some convenient assistance by putting a tiny mark under the last character of the line (see Figure 18).
|
|
That mark provides access to a short menu that lets you bind the selected item. Although you included the CleanCodeControls library in the project, it’s not yet referenced in this source file. So open the menu (Shift-Alt-F10) and choose the binding method you prefer. If you are successful, the SyntaxHilightTextBox name that you typed will now be highlighted in teal, indicating that Visual Studio recognizes it and has bound it correctly. If you selected the using statement for binding, you will find a using CleanCodeControls.Forms; statement inserted in the file prologue (see Figure 19).
Step 5: Fulfill the Parent Contract
Look carefully at Figure 19?Visual Studio provides another hint on the first character of the class name (though it may not show up until you click in the text).
This time the context menu prompts you to implement the abstract class. That is, any time you use an abstract class (or an interface) you must implement whatever that abstract class (or interface) requires. So go ahead and do that. In this case, Visual Studio inserts code for the single required method DefineWordList. The body of the method simply throws an exception. This is not what you want it to do; it is merely inserted as a placeholder, so that if you do not happen to catch and fix it before you run your program you will get an abrupt reminder when the application calls the method.
Step 6: Clean Up
![]() | ? |
Figure 20. Visual Studio Limitation: Visual Studio is not able to display controls inherited from abstract controls in the visual designer; this is the error it shows when you attempt to view the control. |
Technically, this step should be called “Clean Up after Visual Studio.” While Visual Studio has provided useful assistance thus far, it now gets back at you. There are a couple things that it should know that it does not. Switch to the Design tab and you’ll see the glaring error in Figure 20.
The error isn’t caused by you doing anything wrong; by changing the base class away from the default, the Visual Designer is simply not able to display the control in design mode. So just close that tab.
The second surprise comes when you try to compile your program?you get the error shown in Figure 21. Double-click the error line to open the appropriate file to that line, then just delete the offending line; you don’t need it.
![]() | ? |
Figure 21. Compliation Error: The act of changing the parent class to one other than the default causes a compilation error. Click on the error in the Error List to jump to the offending line and just delete it. |
Step 7: Enumerate the Language Keywords
Fill in the body of the DefineWordList method. In this case, you want to include a list of all keywords in VBScript. I extracted my list from the VBScript Language Reference , which is perhaps 95 percent complete; as an exercise you could go through the reference and fill out any missing pieces.
Step 8: Raise the Language Awareness
Customize the control properties by adding these lines to the constructor to make the control VBScript-aware:
EndOfLineCommentMark = "'"; ClosedCommentEndMark = null; ClosedCommentStartMark = null; QuoteChars = """;
These particular properties apply universally in VBScript. That is to say, any time you instantiate a VBScriptSyntaxHilightTextBox you will want it to know that an end-of-line comment is denoted with an apostrophe. So that property is defined here. Other properties?such as color preferences?are not universal. So while you could specify any color preferences here, it is often better to do that when you instantiate the class. Furthermore, doing it then allows you to take advantage of Visual Studio’s property editor, which automatically provides a color picker for you. On the other hand, if you intend to instantiate a VBScriptSyntaxHilightTextBox in several different applications and you want them all to consistently use gray for comments, then you can set it in code.
Creating Your Own Language-Aware IDE
Now that you have a language-aware editor, the next step is to use that as a building block for your IDE. This part of the walk-through takes the editor control you have just created and allows you to execute some or all of its contents within the program.
Step 1: Create a Project
![]() | ? |
Figure 22. Adding Same-Project References: Unlike earlier passes at adding references, here you are adding one from within the same solution. Visual Studio conveniently lists those on the Projects tab so you don’t have to browse. |
Add a new project to your solution (or just start a new solution) that uses the Windows Application template. You’ll find the finished application as the VBScriptTestPlatform project in the downloadable code that accompanies this article.
Step 2: Add a VBScriptSyntaxHilightTextBox for Input
Add a reference to your new control (right-click on References in the Solution Explorer) to make it available (see Figure 22).
The VBScriptSyntaxHilightTextBox control will show up in your toolbox if you added the new project within the same solution. If not, add it by right-clicking in the toolbox and select Choose Items?, and then drag a VBScriptSyntaxHilightTextBox onto your form. The control will act as the input control for you VBScript language editor.
Step 3: Add a StyleAwareRichTextBox for Output
When you added controls to your toolbox at the beginning of the article (from the CleanCodeControls library), one of those added was a StyleAwareRichTextBox. If it is not in your toolbox, add it now. Drag a StyleAwareRichTextBox onto your form. (See the StyleAwareRichTextBox API for more details.) This control acts as a console for messages and execution output. The earlier demo used a DataGridView, which was appropriate for SQL output, but here you need a scrolling text window.
![]() | ? |
Figure 23. VBScript Editor: A trial run of the VBScriptIdeDemo showing the automatic highlighting that occurs after typing in some code. |
Step 4: Test the Program
At this point you could run the program just to see how the VBScript editor works. Type in some code and you will see it highlight comments, keywords, and strings, as shown in Figure 23. (If you have the AutoHilight property on, this will happen as you type; if not press Control-Shift-H to highlight on demand.)
Step 5: Add Other Bells and Whistles
The sample application includes a Button and a SplitContainer just as in the earlier demo. Finally, I have added a CheckBox labeled “Clear on Execute” whose purpose is to empty the output window each time your press the Execute button. That makes the output less cluttered, but making it user-selectable gives the user a choice of whether to start each program run with a cleared output pane.
Step 6: Write the Execution Code
The top pane on the form provides your VBScript editor. The bottom pane provides your output window. So all that you need to do is implement the code behind the Execute button. Note that you actually needed no (user-written) code at all for just the editor. All the code in this demo project is for executing the Visual Basic code and displaying the results in the output window. It is quite brief; here’s the entire program:
public partial class Form1 : Form { private const string SCRIPT_ENGINE = "cscript.exe"; private string tempfile; private CleanCode.IO.ExecProcess runner; public Form1() { InitializeComponent(); tempfile = Path.Combine(Path.GetTempPath(), "temp.vbs"); runner = new CleanCode.IO.ExecProcess( SCRIPT_ENGINE, tempfile); } private void executeButton_Click(object sender, EventArgs e) { if (clearOnExecuteCheckBox.Checked) { outputTextBox.Clear(); Refresh(); } List lines = RunCode(); // Post-process output to highlight errors. string prefix = runner.ErrPrefix; foreach (string s in lines) { if (s.StartsWith(prefix)) { string errMsg = s.Substring(prefix.Length); if (errMsg.StartsWith(tempfile)) { errMsg = "Line " + errMsg.Substring(tempfile.Length); } outputTextBox.AppendStylizedText( errMsg + Environment.NewLine, Color.Red); } else { outputTextBox.AppendText(s + Environment.NewLine); } } } private List RunCode() { // Run process and collect output. File.WriteAllText(tempfile, vbScriptTextBox.SelectedOrAllText); runner.CollectJointOutput = true; runner.RunProcess(); File.Delete(tempfile); return runner.JointOutput; } }
This code consists of just a constructor, an event handler for the button, and one support method. The RunCode method is a small but powerful routine that takes all or part of your input VBScript text, executes it, and returns both the stdout and stderr output streams. The executeButton_Click event handler takes that output and fills the output window in the following fashion: regular output (i.e. from the stdout stream) appears in the default font color (in my demo, this is white on a black background), while any unusual output (from the stderr stream) appears in red. Figure 24 shows how the output pane highlights a compilation error, while Figure 25 shows a run-time error. Note that Figure 25 shows partial output from the program before it encountered the run-time error. The StyleAwareRichTextBox used for the output pane supports the color-coding, which is why I used that control rather than a standard RichTextBox or TextBox.
|
|
You’ll find the ExecProcess class used to execute VBScript code in the CleanCode library download. The ExecProcess class runs any external program and returns its output streams, either as a single, combined output stream as shown here, or as individual stdout and stderr output streams. If instead of a single output pane, for example, you wanted to have one output pane for regular output and a separate window, tab, pop-up, etc. for error output, you could instead instruct the class to collect individual streams and return them separately. See the ExecProcess API for more details.
To execute a VBScript program, you use the Windows Script Host engine (cscript.exe) by passing it a file name on the command line. So the RunCode method creates a temporary file with the user’s selected text. After it runs the code it deletes the temporary file. Note that the constructor includes a line of code that initializes the ExecProcess instance, telling it what program and what file to run.
The Execute button event handler performs these steps:
![]() | ? |
Figure 26. Successive Runs: In this example, the Clear on Execute checkbox is not checked, so the output from successive executions accumulates in the output pane. |
- It first checks whether the user checked “Clear on Execute.” If so, it erases the output window before refilling it. Figures 24 and 25, you will note, show output with the box checked; that way the output is limited to just the current run. Figure 26, on the other hand, has the box unchecked, so you can retain past runs in the output window.
- Next, it runs the VBScript code entered by the user by invoking RunCode.
- After it has the output in hand, it goes through it line by line, deciding whether it is tagged as a line from stderr or not. (When using a combined stream, the ExecProcess class tags lines from stderr with the ErrPrefix property string so you can distinguish them from stdout lines.)
- If the line is tagged (indicating a line from stderr), the tag is removed. Next, the line is further pruned to remove the full path and name of the temporary file that the program used?no need to bother users with that irrelevant information. Finally, it uses a feature of the StyleAwareRichTextBox to color the line to differentiate it from standard output.
- If the line is not tagged, the method simply outputs it directly to the window.
Possible Uses
I hope you can see how the controls and Visual Studio create the potential for simple yet elegant designs. You could, for example, create an IDE for the dual code-and-WYSIWYG HTML editor I mentioned at the beginning of the article. With key building blocks such as the VBScriptSyntaxHilightTextBox, StyleAwareRichTextBox, and ExecProcess classes, you can craft an editor or a basic IDE quickly. I think you’ll find these to be powerful user interface components for many types of programs.