Editable Text Controls: TextBoxBase

Editable Text Controls TextBoxBase

In addition to all of the controls that have a run-of-the-mill Text property, there are several controls whose sole purpose is to display editable text. The Label control displays text, but is read-only.

The TextBoxBase class provides a base class for three controls that allow the read-write display of text:

TextBox

Displays text with no formatting other than the current Font of the control. Has a 64K character capacity limit.

DataGridTextBox

A text box hosted in a DataGridTextBoxColumn control. Derives from TextBox.

RichTextBox

Displays text with formatting.

Figure 12-1 shows the class hierarchy of the TextBoxBase class.

Figure 12-1. TextBoxBase class hierarchy

12.2.1 Properties and Methods

The TextBoxBase class has many properties and methods in addition to those inherited from Control. Many of the properties are listed in Table 12-1 and the methods in Table 12-4. Many of the most commonly used properties and methods are demonstrated in the examples that follow.

Table 12-1. TextBoxBase properties

Property

Value type

Description

AcceptsTab

Boolean

Read/write. Applies to multiline TextBoxes and RichTextBoxes. If true, the Tab key is accepted as part of the text string, otherwise it moves the focus to the next control in the tab order. The default is false.

AutoSize

Boolean

Read/write. If true (the default), the size of control is automatically adjusted when the font changes.

BackColor

Color

Read/write. The background color of the text box. If set the same as the ForeColor property, the text will not be visible. Overrides the Control base class.

BorderStyle

BorderStyle

Read/write. Border style for the label. Values are members of the BorderStyle enumeration listed in Table 12-2. The default value is BorderStyle.Fixed3D.

CanUndo

Boolean

Read-only. If false (set by ClearUndo method), the user cannot undo the previous operation. If true, the Undo method can be used.

ForeColor

Color

Read/write. The foreground color of the text boxthe color of the text Overrides the Control base class. If set the same as the BackColor property, the text will not be visible.

HideSelection

Boolean

Read/write. If true (the default), selected text will cease to be highlighted when the text box loses focus.

Lines

String array

Read/write. An array that contains the text contained in the text box. Each array element corresponds to a line of text.

MaxLength

Integer

Read/write. The maximum number of characters that can be typed into a control.

Modified

Boolean

Read/write. If false (the default), the contents of the text box have not been modified by either the user or the program. Typically used to trigger a save or validation operation.

Multiline

Boolean

Read/write. If true, the text box will display multiple lines of text, interpreting the Enter key as a newline character. If false, the Enter key is disallowed. The default is false for TextBox and DataGridTextBox, and true for RichTextBox.

PreferredHeight

Integer

Read-only. The preferred height of a single-line text box using the current font, in pixels.

ReadOnly

Boolean

Read/write. If false (the default), the Text property can be changed at runtime by the user. If true, the Text property can not be changed by the user, but can still be changed programmatically. The contents of the Text property can be copied by the user despite this value.

ScrollBars

ScrollBars

Read/write. Indicates what type of scrollbar, if any, the text box will have. Valid values must be members of the ScrollBars enumeration, listed in Table 12-3. The default is ScrollBars.None.

SelectedText

String

Read/write. The currently selected text in the text box.

SelectionLength

Integer

Read/write. The length of the SelectedText property.

SelectionStart

Integer

Read/write. The zero-based starting index of the text selected in the text box, if any. If no text is selected, then it is the insertion point for new text. If all the text is selected or if the insertion point is at the beginning of the text box, it returns a value of zero.

Text

String

Read/write. The text displayed by the text box. Overrides the Control base class.

WordWrap

Boolean

Read/write. Applies only when the MultiLine property is true. If true (the default), the contents of the text box will wrap words if the length of a string exceeds the width of the control. If false, the text box will scroll horizontally as the text is entered to display the right end of the text.

Table 12-2. BorderStyle enumeration values

Value

Description

Fixed3D

3-D border

FixedSingle

Single-line border

None

No border

Table 12-3. ScrollBars enumeration

Value

Description

Both

Both horizontal and vertical scrollbars are present. A horizontal scrollbar is present only if the WordWrap property is set to false.

Horizontal

Only a horizontal scrollbar is present, and then only if the WordWrap property is set to false.

None

No scrollbars are present.

Vertical

Only a vertical scrollbar is present.

Table 12-4. TextBoxBase methods

Method

Description

AppendText

Appends text string contained in the argument to the Text property of the text box. Functionally equivalent to using the concatenation operator, although it is actually implemented by setting the selection to the end of the text and then replacing the selection with the text to be appended.

Clear

Empties the Text property of the text box. Equivalent to setting the Text property to an empty string. Cannot be undone by calling the Undo method.

ClearUndo

Removes the information about the most recent operation from the undo buffer so that the undo operation cannot be repeated. Sets the read-only property CanUndo property to false.

Copy

Copies the currently selected text in the text box control to the Clipboard. Default functionality provided by Ctrl-C.

Cut

Moves the currently selected text from the text box control to the Clipboard. Default functionality provided by Ctrl-X.

Paste

Replaces the text currently selected in the text box with text currently stored in the Clipboard. Default functionality provided by Ctrl-V.

ScrollToCaret

Scrolls the contents of the text box until the caret (the current text entry point) is visible in the control. If the caret is currently below the visible region, the contents will be scrolled until the caret is at the bottom of the control. If the caret is currently above the visible region, the contents will be scrolled until the caret is at the top of the control. If the caret is currently visible or if the control does not have focus, the method will be have no effect.

Select

Overloaded method for selecting text within a text box control. Takes two integer arguments representing the starting character (zero-based) and the number of characters to be selected.

SelectAll

Selects the entire Text property of a text box control. Default functionality provided by Ctrl-A.

Undo

Undoes the last clipboard or text change operation performed on the contents of the text box control if the CanUndo property is true. Default functionality provided by Ctrl-Z.

The program shown in Example 12-1 (in C#) and Example 12-2 (in VB.NET) demonstrates many of the properties and methods of the TextBoxBase class. These examples have two TextBox controls for entering textone single line and one multiline. A button can display, in another text box, the contents of the Lines array from the multiline text box. A simple menu allows various typical text manipulations, such as copy, cut, paste, and clear all. An analysis of the program follows.

Many of the methods listed in Table 12-4 are implemented in the base class, so there is no need for you to supply code to provide their functionality. These methods include Copy, Cut, Paste, SelectAll, and Undo.

When the program is compiled and run and some text is entered into the multiline text box, it looks like Figure 12-2.

Figure 12-2. TextBoxes

Example 12-1. TextBox control properties and in C# (TextBoxes.cs) methods

using System; using System.Drawing; using System.Windows.Forms; using System.Text; namespace ProgrammingWinApps { public class TextBoxes : Form { int yDelta; int yPos = 20; TextBox txtSingle; TextBox txtMulti; TextBox txtDisplay; Button btn; TextBox[ ] txtBoxes = new TextBox[2]; public TextBoxes( ) { Text = "TextBoxes"; Size = new Size(450,375); Label lblSingle = new Label( ); lblSingle.Parent = this; lblSingle.Text = "Single Line TextBox:"; lblSingle.Location = new Point(10,yPos); lblSingle.Size = new Size(150,20); lblSingle.TextAlign = ContentAlignment.MiddleRight; yDelta = lblSingle.Height + 10; txtSingle = new TextBox( ); txtSingle.Parent = this; txtSingle.Text = "Single Line"; txtSingle.Size = new Size(200, txtSingle.PreferredHeight); txtSingle.Location = new Point(lblSingle.Left + lblSingle.Size.Width, yPos); txtSingle.Multiline = false; txtSingle.BorderStyle = BorderStyle.Fixed3D; Label lblMulti = new Label( ); lblMulti.Parent = this; lblMulti.Text = "Multi Line TextBox:"; lblMulti.Location = new Point(10, yPos + yDelta); lblMulti.Size = new Size(150,20); lblMulti.TextAlign = ContentAlignment.MiddleRight; txtMulti = new TextBox( ); txtMulti.Parent = this; txtMulti.Text = "Multi Line"; txtMulti.Size = new Size(200,100); txtMulti.Location = new Point(lblMulti.Left + lblMulti.Size.Width, yPos + yDelta); txtMulti.AcceptsTab = true; txtMulti.Multiline = true; txtMulti.BorderStyle = BorderStyle.Fixed3D; txtMulti.ScrollBars = ScrollBars.Vertical; btn = new Button( ); btn.Parent = this; btn.Text = "Show MultiLines"; btn.Location = new Point(lblMulti.Left + lblMulti.Size.Width, yPos + (5 * yDelta)); btn.Click += new System.EventHandler(btn_Click); int xSize = ((int)(Font.Height * .75) * btn.Text.Length); int ySize = Font.Height + 10; btn.Size = new Size(xSize, ySize); txtDisplay = new TextBox( ); txtDisplay.Parent = this; txtDisplay.Text = ""; txtDisplay.Size = new Size(200,100); txtDisplay.Location = new Point(lblMulti.Left + lblMulti.Size.Width, yPos + (6 * yDelta)); txtDisplay.Multiline = true; txtDisplay.BorderStyle = BorderStyle.FixedSingle; txtDisplay.ScrollBars = ScrollBars.Vertical; txtDisplay.ReadOnly = true; // Fill the array of TextBoxes txtBoxes[0] = txtSingle; txtBoxes[1] = txtMulti; // Menus // Edit menu items MenuItem mnuDash1 = new MenuItem("-"); MenuItem mnuDash2 = new MenuItem("-"); MenuItem mnuUndo = new MenuItem("&Undo", new EventHandler(mnuUndo_Click), Shortcut.CtrlZ); MenuItem mnuCut = new MenuItem("Cu&t", new EventHandler(mnuCut_Click), Shortcut.CtrlX); MenuItem mnuCopy = new MenuItem("&Copy", new EventHandler(mnuCopy_Click), Shortcut.CtrlC); MenuItem mnuPaste = new MenuItem("&Paste", new EventHandler(mnuPaste_Click), Shortcut.CtrlV); MenuItem mnuDelete = new MenuItem("&Delete", new EventHandler(mnuDelete_Click)); MenuItem mnuSelectAll = new MenuItem("Select &All", new EventHandler(mnuSelectAll_Click), Shortcut.CtrlA); MenuItem mnuSelect5 = new MenuItem("Select First &5", new EventHandler(mnuSelect5_Click), Shortcut.Ctrl5); MenuItem mnuClear = new MenuItem("Clea&r", new EventHandler(mnuClear_Click)); MenuItem mnuEdit = new MenuItem("&Edit", new MenuItem[ ] {mnuUndo, mnuDash1, mnuCut, mnuCopy, mnuPaste, mnuDelete, mnuDash2, mnuSelectAll, mnuSelect5, mnuClear}); // View Menu items MenuItem mnuScrollToCaret = new MenuItem("&Scroll to Caret", new EventHandler(mnuScrollToCaret_Click)); MenuItem mnuShowSelectionStart = new MenuItem( "S&how SelectionStart", new EventHandler(mnuShowSelectionStart_Click)); MenuItem mnuView = new MenuItem("&View", new MenuItem[ ] {mnuScrollToCaret, mnuShowSelectionStart}); // Main menu Menu = new MainMenu(new MenuItem[ ] {mnuEdit, mnuView}); } // close for constructor static void Main( ) { Application.Run(new TextBoxes( )); } private void mnuUndo_Click(object sender, EventArgs e) { for (int i = 0; i < txtBoxes.Length; i++) { if (txtBoxes[i].Focused) { TextBox txt = txtBoxes[i]; if (txt.CanUndo = = true) { txt.Undo( ); txt.ClearUndo( ); } } } } private void mnuCut_Click(object sender, EventArgs e) { for (int i = 0; i < txtBoxes.Length; i++) { if (txtBoxes[i].Focused) { TextBox txt = txtBoxes[i]; if (txt.SelectedText != "") txt.Cut( ); } } } private void mnuCopy_Click(object sender, EventArgs e) { for (int i = 0; i < txtBoxes.Length; i++) { if (txtBoxes[i].Focused) { TextBox txt = txtBoxes[i]; if (txt.SelectionLength > 0) txt.Copy( ); } } } private void mnuPaste_Click(object sender, EventArgs e) { if (Clipboard.GetDataObject( ).GetDataPresent(DataFormats.Text) = = true) { for (int i = 0; i < txtBoxes.Length; i++) { if (txtBoxes[i].Focused) { TextBox txt = txtBoxes[i]; if (txt.SelectionLength > 0) { if (MessageBox.Show( "Do you want to overwrite the currently " + "selected text?", "Cut & Paste", MessageBoxButtons.YesNo) = = DialogResult.No) txt.SelectionStart = txt.SelectionStart + txt.SelectionLength; } txt.Paste( ); } } } } private void mnuDelete_Click(object sender, EventArgs e) { for (int i = 0; i < txtBoxes.Length; i++) { if (txtBoxes[i].Focused) { TextBox txt = txtBoxes[i]; if (txt.SelectionLength > 0) txt.SelectedText = ""; } } } private void mnuClear_Click(object sender, EventArgs e) { for (int i = 0; i < txtBoxes.Length; i++) { if (txtBoxes[i].Focused) { TextBox txt = txtBoxes[i]; txt.Clear( ); } } } private void mnuSelect5_Click(object sender, EventArgs e) { for (int i = 0; i < txtBoxes.Length; i++) { if (txtBoxes[i].Focused) { TextBox txt = txtBoxes[i]; if (txt.Text.Length >= 5) { txt.Select(0,5); } else { txt.Select(0,txt.Text.Length); } } } } private void mnuSelectAll_Click(object sender, EventArgs e) { for (int i = 0; i < txtBoxes.Length; i++) { if (txtBoxes[i].Focused) { TextBox txt = txtBoxes[i]; txt.SelectAll( ); } } } private void mnuScrollToCaret_Click(object sender, EventArgs e) { for (int i = 0; i < txtBoxes.Length; i++) { if (txtBoxes[i].Focused) { TextBox txt = txtBoxes[i]; txt.ScrollToCaret( ); } } } private void mnuShowSelectionStart_Click(object sender, EventArgs e) { MessageBox.Show("SelectionStart: " + txtSingle.SelectionStart.ToString( )); } private void btn_Click(object sender, EventArgs e) { // Create a string array to hold the Lines property. string[ ] arLines = new string [txtMulti.Lines.Length]; arLines = txtMulti.Lines; // Use stringBuilder for efficiency. string str = "Line String "; StringBuilder sb = new StringBuilder( ); sb.Append(str); // Iterate through the array & display each line. for (int i=0; i < arLines.Length; i++) { str = i.ToString( ) + ". " + arLines[i] + " "; sb.Append(str); } txtDisplay.Text = sb.ToString( ); } } // close for form class } // close form namespace

Example 12-2. TextBox control properties and in VB.NET (TextBoxes.vb) methods

Option Strict On imports System imports System.Drawing imports System.Windows.Forms imports System.Text namespace ProgrammingWinApps public class TextBoxes : inherits Form dim yDelta as integer dim yPos as integer = 20 dim txtSingle as TextBox dim txtMulti as TextBox dim txtDisplay as TextBox dim btn as Button dim txtBoxes(1) as TextBox public sub New( ) Text = "TextBoxes" Size = new Size(450,375) dim lblSingle as new Label( ) lblSingle.Parent = me lblSingle.Text = "Single Line TextBox:" lblSingle.Location = new Point(10,yPos) lblSingle.Size = new Size(150,20) lblSingle.TextAlign = ContentAlignment.MiddleRight yDelta = lblSingle.Height + 10 txtSingle = new TextBox( ) txtSingle.Parent = me txtSingle.Text = "Single Line" txtSingle.Size = new Size(200, txtSingle.PreferredHeight) txtSingle.Location = new Point(lblSingle.Left + _ lblSingle.Size.Width, yPos) txtSingle.Multiline = false txtSingle.BorderStyle = BorderStyle.Fixed3D dim lblMulti as new Label( ) lblMulti.Parent = me lblMulti.Text = "Multi Line TextBox:" lblMulti.Location = new Point(10, yPos + yDelta) lblMulti.Size = new Size(150,20) lblMulti.TextAlign = ContentAlignment.MiddleRight txtMulti = new TextBox( ) txtMulti.Parent = me txtMulti.Text = "Multi Line" txtMulti.Size = new Size(200,100) txtMulti.Location = new Point(lblMulti.Left + _ lblMulti.Size.Width, yPos + yDelta) txtMulti.AcceptsTab = true txtMulti.Multiline = true txtMulti.BorderStyle = BorderStyle.Fixed3D txtMulti.ScrollBars = ScrollBars.Vertical btn = new Button( ) btn.Parent = me btn.Text = "Show MultiLines" btn.Location = new Point(lblMulti.Left + _ lblMulti.Size.Width, yPos + (5 * yDelta)) AddHandler btn.Click, AddressOf btn_Click dim xSize as integer = CType((Font.Height * .75) * _ btn.Text.Length, integer) dim ySize as integer = Font.Height + 10 btn.Size = new Size(xSize, ySize) txtDisplay = new TextBox( ) txtDisplay.Parent = me txtDisplay.Text = "" txtDisplay.Size = new Size(200,100) txtDisplay.Location = new Point(lblMulti.Left + _ lblMulti.Size.Width, yPos + (6 * yDelta)) txtDisplay.Multiline = true txtDisplay.BorderStyle = BorderStyle.FixedSingle txtDisplay.ScrollBars = ScrollBars.Vertical txtDisplay.ReadOnly = true ' Fill the array of TextBoxes txtBoxes(0) = txtSingle txtBoxes(1) = txtMulti ' Menus ' Edit menu items dim mnuDash1 as new MenuItem("-") dim mnuDash2 as new MenuItem("-") dim mnuUndo as new MenuItem("&Undo", _ new EventHandler(AddressOf mnuUndo_Click), _ Shortcut.CtrlZ) dim mnuCut as new MenuItem("Cu&t", _ new EventHandler(AddressOf mnuCut_Click), _ Shortcut.CtrlX) dim mnuCopy as new MenuItem("&Copy", _ new EventHandler(AddressOf mnuCopy_Click), _ Shortcut.CtrlC) dim mnuPaste as new MenuItem("&Paste", _ new EventHandler(AddressOf mnuPaste_Click), _ Shortcut.CtrlV) dim mnuDelete as new MenuItem("&Delete", _ new EventHandler(AddressOf mnuDelete_Click)) dim mnuSelectAll as new MenuItem("Select &All", _ new EventHandler(AddressOf mnuSelectAll_Click), _ Shortcut.CtrlA) dim mnuSelect5 as new MenuItem("Select First &5", _ new EventHandler(AddressOf mnuSelect5_Click), _ Shortcut.Ctrl5) dim mnuClear as new MenuItem("Clea&r", _ new EventHandler(AddressOf mnuClear_Click)) dim mnuEdit as new MenuItem("&Edit", _ new MenuItem( ) {mnuUndo, mnuDash1, _ mnuCut, mnuCopy, mnuPaste, mnuDelete, mnuDash2, _ mnuSelectAll, mnuSelect5, mnuClear}) ' View Menu items dim mnuScrollToCaret as new MenuItem("&Scroll to Caret", _ new EventHandler(AddressOf mnuScrollToCaret_Click)) dim mnuShowSelectionStart as new MenuItem( _ "&S&how SelectionStart", _ new EventHandler(AddressOf mnuShowSelectionStart_Click)) dim mnuView as new MenuItem("&View", _ new MenuItem( ) {mnuScrollToCaret, _ mnuShowSelectionStart}) ' Main menu Menu = new MainMenu(new MenuItem( ) {mnuEdit, mnuView}) end sub ' close for constructor public shared sub Main( ) Application.Run(new TextBoxes( )) end sub private sub mnuUndo_Click(ByVal sender As Object, _ ByVal e As EventArgs) dim i as integer for i = 0 to txtBoxes.Length - 1 if txtBoxes(i).Focused then dim txt as TextBox = txtBoxes(i) if txt.CanUndo = true then txt.Undo( ) txt.ClearUndo( ) end if end if next end sub private sub mnuCut_Click(ByVal sender As Object, _ ByVal e As EventArgs) dim i as integer for i = 0 to txtBoxes.Length - 1 if txtBoxes(i).Focused then dim txt as TextBox = txtBoxes(i) if txt.SelectedText <> "" then txt.Cut( ) end if end if next end sub private sub mnuCopy_Click(ByVal sender As Object, _ ByVal e As EventArgs) dim i as integer for i = 0 to txtBoxes.Length - 1 if txtBoxes(i).Focused then dim txt as TextBox = txtBoxes(i) if txt.SelectionLength > 0 then txt.Copy( ) end if end if next end sub private sub mnuPaste_Click(ByVal sender As Object, _ ByVal e As EventArgs) if Clipboard.GetDataObject( ).GetDataPresent(DataFormats.Text) = _ true then dim i as integer for i = 0 to txtBoxes.Length - 1 if txtBoxes(i).Focused then dim txt as TextBox = txtBoxes(i) if txt.SelectionLength > 0 then if MessageBox.Show( _ "Do you want to overwrite the currently " + _ "selected text?", _ "Cut & Paste", MessageBoxButtons.YesNo) = _ DialogResult.No then txt.SelectionStart = txt.SelectionStart + _ txt.SelectionLength end if end if txt.Paste( ) end if next end if end sub private sub mnuDelete_Click(ByVal sender As Object, _ ByVal e As EventArgs) dim i as integer for i = 0 to txtBoxes.Length - 1 if txtBoxes(i).Focused then dim txt as TextBox = txtBoxes(i) if txt.SelectionLength > 0 then txt.SelectedText = "" end if end if next end sub private sub mnuClear_Click(ByVal sender As Object, _ ByVal e As EventArgs) dim i as integer for i = 0 to txtBoxes.Length - 1 if txtBoxes(i).Focused then dim txt as TextBox = txtBoxes(i) txt.Clear( ) end if next end sub private sub mnuSelect5_Click(ByVal sender As Object, _ ByVal e As EventArgs) dim i as integer for i = 0 to txtBoxes.Length - 1 if txtBoxes(i).Focused then dim txt as TextBox = txtBoxes(i) if txt.Text.Length >= 5 then txt.Select(0,5) else txt.Select(0,txt.Text.Length) end if end if next end sub private sub mnuSelectAll_Click(ByVal sender As Object, _ ByVal e As EventArgs) dim i as integer for i = 0 to txtBoxes.Length - 1 if txtBoxes(i).Focused then dim txt as TextBox = txtBoxes(i) txt.SelectAll( ) end if next end sub private sub mnuScrollToCaret_Click(ByVal sender As Object, _ ByVal e As EventArgs) dim i as integer for i = 0 to txtBoxes.Length - 1 if txtBoxes(i).Focused then dim txt as TextBox = txtBoxes(i) txt.ScrollToCaret( ) end if next end sub private sub mnuShowSelectionStart_Click(ByVal sender As Object, _ ByVal e As EventArgs) MessageBox.Show("SelectionStart: " + _ txtSingle.SelectionStart.ToString( )) end sub private sub btn_Click(ByVal sender as object, _ ByVal e as EventArgs) ' Create a string array to hold the Lines property. dim arLines(txtMulti.Lines.Length - 1) as string arLines = txtMulti.Lines ' Use stringBuilder for efficiency. dim str as string = "Line" + vbTab + "String" + vbCrLf dim sb as new StringBuilder( ) sb.Append(str) ' Iterate through the array & display each line. dim i as integer for i = 0 to arLines.Length - 1 str = i.ToString( ) + "." + vbTab + arLines(i) + vbCrLf sb.Append(str) next txtDisplay.Text = sb.ToString( ) end sub end class end namespace

The program defines a Form class called TextBoxes, with member variables representing two integers used for calculating the Location property of all the controls, three text boxes, a button, and an array of TextBoxes.

This array of TextBoxes will be used by most menu event handlers when determining which text box should be the target of the pending action. The array declaration looks like this:

TextBox[ ] txtBoxes = new TextBox[2];

dim txtBoxes(1) as TextBox

Remember that arrays in C# are declared with the count of elements, but they are declared with the upper bound in VB.NET. All arrows are zero-indexed in both languages.

Inside the constructor, each control is instantiated and specified with several properties. Labels used for captioning purposes are declared and instantiated here. They are not defined as member variables since they are never referenced outside the constructor. (Had this code been written in Visual Studio .NET, all the controls would be member variables.)

The first TextBox control, txtSingle, is a single line text box: its Multiline property is set to false. The BorderStyle property is set to BorderStyle.Fixed3D to give it a chiseled look.

The second TextBox control, txtMulti, is a multiline text box, since its Multiline property is set to true. In addition to the same BorderStyle setting as the previous control, txtMulti has the AcceptTab property set to true and the ScrollBars property set to ScrollBars.Vertical (see Tables Table 12-2 and Table 12-3).

The AcceptTab property causes the Tab key to be entered as a tab character in the text rather than shifting focus to the next control in the tab order. The ScrollBars property displays a vertical scrollbar in the text box. If there is insufficient text to warrant scrolling, the scrollbars are disabled (visible but grayed), otherwise they are enabled.

The button control is straightforward, with it's size based dynamically on the current font and the length of the Text property and an event handler for the Click event.

The final TextBox control, txtDisplay, displays the formatted contents of the Lines array from txtMulti. Since it is for display only, its ReadOnly property is set to true. Setting the ReadOnly property to true also makes the background color light gray without the need to explicitly set the BackColor property. Also, to distinguish the read-only text box visually, the BorderStyle is set to BorderStyle.FixedSingle.

The next two lines of code populate the array of TextBoxes previously declared as a member variable:

txtBoxes[0] = txtSingle; txtBoxes[1] = txtMulti;

txtBoxes(0) = txtSingle txtBoxes(1) = txtMulti

The next block of code in the constructor creates the menu system. A menu consists of MenuItem objects, which are assembled into higher level MenuItem objects, which are themselves assembled into a main menu. In this example, the main menu consists of two menu items: Edit and View. The Edit menu item has the following menu items: Undo, Cut, Copy, Paste, Delete, Select All, Select First 5, and Clear, along with two separators. The View menu has two menu items: View to Caret and Show SelectionStart.

Menus will be covered in detail in Chapter 18.

All the menu items can have an accelerator key, an underlined character that can be pressed in conjunction with the Alt key to effectuate that menu item. Many menu items also have a shortcut key, a single key or key combination listed to the right of the menu item, which will effectuate the command.

Menu are typically built backwardsthe menu items deepest in the hierarchy are created first, and then they are added to the next level up in the hierarchy. These items are then added to their parent items, and so on up the main menu.

It is not necessary to build the menu backward. You only need to create the children before adding them to the parent. However, you can create the items in any order you wish.

Different menu item constructors are used. All the menu items that actually do something have a specified event handler that will be invoked if that menu item is clicked. For example, the Undo menu item is defined in the following code snippet:

MenuItem mnuUndo = new MenuItem("&Undo", new EventHandler(mnuUndo_Click), Shortcut.CtrlZ);

dim mnuUndo as new MenuItem("&Undo", _ new EventHandler(AddressOf mnuUndo_Click), _ Shortcut.CtrlZ)

The ampersand before the U in Undo causes that letter to appear underlined and indicates that U is an accelerator key. The event handler for this menu item is mnuUndo_Click, and the shortcut key is Ctrl-Z.

Most commands under the Edit menu item have standard Windows shortcut keys: Ctrl-Z for Undo, Ctrl-X for Cut, Ctrl-V for Paste, and so on. Since these are standard shortcuts, they will still work even if they are not explicitly declared when the menu item is created. Explicitly declaring them makes them visible when the menu is displayed, reminding the user that they exist.

There is another, more subtle reason for explicitly declaring the shortcut. The code used here does not exactly duplicate the standard Windows behavior for all operationsfor example, Paste asks for user confirmation, while standard Windows Paste just pastes. If the shortcut were not explicitly declared, the user would see standard Windows behavior when using the shortcut and customized behavior when using the menu. Explicitly declaring the shortcut forces both techniques to use your code.

Similarly, in this example the Delete menu item has no declared shortcut key, yet the Delete key works as you would expect. On the other hand, the Select First 5 menu item, which selects the first five characters of the text box, is not a standard Windows command and does not have a standard shortcut. If the shortcut key Ctrl-5 were not explicitly declared in its menu item declaration, there would be no shortcut key.

All menu item event handlers follow the same pattern. The event handler is invoked in response to a menu click; it does not inherently know which text box is the target. Therefore, each method must determine which text box to apply the action to, and whether that action is to undo, cut, copy, or paste. It determines the target text box by iterating through the array of TextBoxes, txtBoxes, testing each one to see if it has focus. If it does, then it applies whatever code is relevant for that event handler. It has the following design pattern:

for (int i = 0; i < txtBoxes.Length; i++) { if (txtBoxes[i].Focused) { // Instantiate variable for the target TextBox TextBox txt = txtBoxes[i]; // Take the action here } }

dim i as integer for i = 0 to txtBoxes.Length - 1 if txtBoxes(i).Focused then ' Instantiate variable for the target TextBox dim txt as TextBox = txtBoxes(i) ' Take the action here end if next

The highlighted line assigns a reference to the array element that reference is assigned, and the code can then take the appropriate action, typically by calling a TextBoxBase instance method or by setting a TextBoxBase property (treating the TextBox polymorphically).

The Undo menu item event handler, mnuUndo_Click, uses the following lines of code to implement its action:

if (txt.CanUndo = = true) { txt.Undo( ); txt.ClearUndo( ); }

if txt.CanUndo = true then txt.Undo( ) txt.ClearUndo( ) end if

First test the CanUndo property to determine if there is anything to undo. If so, the Undo method is called, followed by the ClearUndo method to reset CanUndo to false.

The Cut menu item event handler, mnuCut_Click, uses the following lines of code to implement its action:

if (txt.SelectedText != "") txt.Cut( );

if txt.SelectedText <> "" then txt.Cut( ) end if

This method tests to see whether the SelectedText property is an empty string. If not, then there must be selected text, and the Cut method is called to remove that selection from the text box and place it in the Clipboard.

The Copy menu item event handler, mnuCopy_Click, uses the following lines of code to implement its action:

if (txt.SelectionLength > 0) txt.Copy( );

if txt.SelectionLength > 0 then txt.Copy( ) end if

This method is slightly different from the Cut implementation, testing whether the SelectionLength property is greater than zero. If so, there is some selected text, and the Copy method is called to copy that selection into the Clipboard.

The two different tests used in the Cut and Copy methods are functionally equivalent. Which one you decide to use is a matter of programmer preference.

The Paste menu item event handler, mnuPaste_Click, differs from the standard design pattern in that it wraps the entire thing inside an if statement to test for text data in the Clipboard (see Sidebar 12-1 for an explanation of this):

if (Clipboard.GetDataObject( ).GetDataPresent(DataFormats.Text) = = true) {

if Clipboard.GetDataObject( ).GetDataPresent(DataFormats.Text) _ = true then

If there is text data in the Clipboard, then the Paste operation can proceed, using the same design pattern as the other methods. After iterating through the array of TextBoxes, finding the text box with focus, and getting a reference to that control, the method then implements a slightly more cautious behavior than the standard Windows paste operation. If you currently have text selected, the typical Windows paste operation simply overwrites that selection with the contents of the Clipboard. Here it asks first whether you want to overwrite. If the answer is No, then it uses the current SelectionStart and SelectionLength properties to calculate a new value for SelectionStart before calling the TextBoxBase method Paste. This action pastes the contents of the Clipboard at the end of the currently selected text, rather than replacing the currently selected text:

The Windows Clipboard

The Clipboard is a standard Windows system object: a shared chunk of memory used for temporarily storing data used in cut, copy, and paste operations. It is implemented in the .NET Framework through the Clipboard class, which provides two methods: SetDataObject places data in the Clipboard, and GetDataObject retrieves data from the Clipboard.

The overloaded SetDataObject method has two forms. They are:

public static void SetDataObject(object); public static void SetDataObject(object, bool);

Overloads Public Shared Sub SetDataObject(object) Overloads Public Shared Sub SetDataObject(object, Boolean)

The first form places the object specified in the argument onto the Clipboard until it is replaced by the next object placed on the Clipboard or the application exits. The second form takes a Boolean value, which if true, persists the object on the Clipboard even after the application exits.

The GetDataObject method retrieves the stored object from the Clipboard. The object can be of many different types. It is returned as an object of type IDataObject, which has predefined data formats. These data formats are contained in the DataFormats class as read-only, static (Shared in VB .NET) public fields, listed in Table 12-5.

To retrieve data from the Clipboard, declare an object of type IDataObject and call the static GetDataObject method:

IDataObject iData = Clipb

oard.GetDataObject();

dim iData as IDataObject = Clipboard.GetDataObject()

You can then test the IDataObject to determine whether it is a format your application can use, using the GetDataPresent method of the IDataObject class, which returns a Boolean:

if (iData.GetDataPresent(DataFormats.Text))

if iData.GetDataPresent(DataFormats.Text) then

In the Paste method used in Example 12-1 and Example 12-2, these two steps were combined into a single line of code:

if (Clipboard.GetDataObject().GetDataPresent( DataFormats.Text) == true) {

if Clipboard.GetDataObject().GetDataPresent( _ DataFormats.Text) _= true then

if (txt.SelectionLength > 0) { if (MessageBox.Show( "Do you want to overwrite the currently selected text?", "Cut & Paste", MessageBoxButtons.YesNo) = = DialogResult.No) txt.SelectionStart = txt.SelectionStart + txt.SelectionLength; } txt.Paste( );

if txt.SelectionLength > 0 then if MessageBox.Show( _ "Do you want to overwrite the currently selected text?", _ "Cut & Paste", MessageBoxButtons.YesNo) = _ DialogResult.No then txt.SelectionStart = txt.SelectionStart + _ txt.SelectionLength end if end if txt.Paste( )

Table 12-5. DataFormats Formats (public fields)

Format

Description

Bitmap

Windows bitmap.

CommaSeparatedValue

Comma-separated value (CSV) format used by spreadsheets.

Dib

Windows Device Independent Bitmap (DIB).

Dif

Windows Data Interchange Format (DIF). Not used directly by Windows Forms.

EnhancedMetafile

Windows enhanced metafile format.

FileDrop

Windows file drop format. Not used directly by Windows Forms.

Html

Text consisting of HTML data.

Locale

Windows culture format. Not used directly by Windows Forms.

MetafilePict

Windows metafile format. Not used directly by Windows Forms.

OemText

Windows original equipment manufacturer (OEM) text format.

Palette

Windows palette format.

PenData

Windows pen data format for handwriting software. Not used by Windows Forms.

Riff

Resource Interchange File Format (RIFF) audio format. Not used directly by Windows Forms.

Rtf

Rich Text Format (RTF).

Serializable

A format that encapsulates any type of object.

StringFormat

Windows Forms string object.

SymbolicLink

Windows symbolic link format. Not used directly by Windows Forms.

Text

Standard ANSI text.

Tiff

Tagged Image File Format (TIFF) image. Not used directly by Windows Forms.

UnicodeText

Windows Unicode text format.

WaveAudio

Wave audio format. Not used directly by Windows Forms.

The Delete menu item event handler, mnuDelete_Click, uses the following lines of code to implement its action:

if (txt.SelectionLength > 0) txt.SelectedText = "";

if txt.SelectionLength > 0 then txt.SelectedText = "" end if

It uses the SelectionLength property to see if anything has been selected. If so, it deletes it by setting the SelectedText property to an empty string.

The Clear menu item event handler, mnuClear_, is very simple. It calls the Clear method to empty the Text property of the text box (the same in both languages except for the trailing semicolon):

txt.Clear( );

The Select First 5 menu item selects the first five characters of the text box. If there are fewer than five characters in the text box, it selects them all:

if (txt.Text.Length >= 5) { txt.Select(0,5); } else { txt.Select(0,txt.Text.Length); }

if txt.Text.Length >= 5 then txt.Select(0,5) else txt.Select(0,txt.Text.Length) end if

In either case, it uses the Select method, which takes two integer arguments. The first integer is the zero-based index of the first character to be selected, and the second integer is the number of characters to be selected.

The Select All menu item is implemented using the SelectAll method, which selects the entire contents of the text box.

The Scroll to Caret menu item under the View menu is implemented in the mnuScrollToCaret method, invoking the ScrollToCaret method. To see this item in operation, enter enough lines of text in the multiline text box so that the scrollbar is operative. Then leaving the caret i.e., the text cursor near either the top or bottom of the text box, use the scrollbar to scroll away so that the caret is no longer visible. Select the Scroll to Caret menu item. Depending on where the caret was positioned relative to the visible text, the text will be scrolled so that the caret is either the first line in the text box or the last.

The final event handler method implements the Show SelectionStart menu item under the View menu. This menu item displays the current value of the SelectionStart property for the single line text box, txtSingle.

The Click event handler for the button control demonstrates the use of the Lines property of a text box. This is an array of strings whose elements each contain one line of text from the Text property of the text box. This array can be iterated and the lines processed one by one.

In this example, a string array is declared to contain all the elements of the Lines array, and then the Lines array is assigned to that array:

string[ ] arLines = new string [txtMulti.Lines.Length]; arLines = txtMulti.Lines;

dim arLines(txtMulti.Lines.Length - 1) as string arLines = txtMulti.Lines

The StringBuilder class efficiently builds up the string that will be displayed in the display text box, txtDisplay. To use the StringBuilder class, reference the System.Text namespace at the beginning of the program:

using System.Text;

imports System.Text

A string variable is declared and instantiated with a string containing a header row. Escape characters are used in C#, while the equivalent VB.NET constants are used in VB.NET. Then the StringBuilder object is instantiated and the string is appended to the StringBuilder:

string str = "Line String "; StringBuilder sb = new StringBuilder( ); sb.Append(str);

dim str as string = "Line" + vbTab + "String" + vbCrLf dim sb as new StringBuilder( ) sb.Append(str)

Then the array of strings is iterated, and a string is created with information about each line of text, consisting of the index number and the contents of each array element. Each string is appended to the StringBuilder:

for (int i=0; i < arLines.Length; i++) { str = i.ToString( ) + ". " + arLines[i] + " "; sb.Append(str); }

dim i as integer for i = 0 to arLines.Length - 1 str = i.ToString( ) + "." + vbTab + arLines(i) + vbCrLf sb.Append(str) next

Finally, the completed StringBuilder object is converted back to a string and displayed in the Text property of the display text box.

txtDisplay.Text = sb.ToString( )

Strictly speaking, it was not necessary to declare and instantiate the intermediate string array, arLines, in this example. That step could have been eliminated by rewriting the iteration, as follows:

for (int i=0; i < txtMulti.Lines.Length; i++) { str = i.ToString( ) + ". " + txtMulti.Lines[i] + " "; sb.Append(str); }

for i = 0 to txtMulti.Lines.Length - 1 str = i.ToString( ) + "." + vbTab + txtMulti.Lines(i) + vbCrLf sb.Append(str) next

The intermediate string array was created in this example to demonstrate other ways of manipulating the Lines property.

12.2.2 Events

The TextBoxBase class contains a large number of events that are either inherited from Control, or like the Click event, are not inherited but behave as if they were. Some of the most commonly used events are listed in Table 12-6.

Table 12-6. TextBoxBase events

Event

Event argument

Description

Click

EventArgs

Raised when the control is clicked. Not inherited from Control.

DoubleClick

EventArgs

Raised when the control is double-clicked. Inherited from Control.

TextChanged

EventArgs

Raised when the Text property is changed. Inherited from Control.

Validated

EventArgs

Raised when the control is done validating. Inherited from Control.

Validating

CancelEventArgs

Raised while the control is validating. Inherited from Control.

Events are covered thoroughly in Chapter 4. That chapter devotes an entire section to the events common to all controls, including text boxes. It also includes an example that uses the Validating event to validate the contents of a text box.

In Example 12-3, a form contains a text box and a button captioned Save. Clicking the Save button causes the contents of the text box to be saved only if the contents have been changed since the last time the Save button was clicked. (In this example, nothing is actually saved. There is some code that simulates the save operation.) The Modified property is tested to see whether the save operation needs to be performed. The TextChanged event resets the Modified property to false if the changes bring the Text property back to the same value it had the last time it was saved.

The programs listed in Example 12-3 and Example 12-4 use some of the techniques described in Chapter 7 to dynamically size and position the text box and button controls on the form. No matter how the form is resized by the user, the text box fills the entire client area, leaving space for the centered button at the bottom.

Example 12-3. TextBoxBase Modified property C# and TextChanged event in C# (TextBoxTextChanged.cs)

using System; using System.Drawing; using System.Windows.Forms; namespace ProgrammingWinApps { public class TextBoxTextChanged : Form { TextBox txt; Button btn; string strOriginal; public TextBoxTextChanged( ) { Text = "TextBox Modified and TextChanged"; Size = new Size(300, 375); txt = new TextBox( ); txt.Parent = this; txt.Text = "Enter text here."; txt.Size = new Size(ClientSize.Width - 20, ClientSize.Height - 100); txt.Location = new Point(10,10); txt.TextChanged += new System.EventHandler(txt_TextChanged); txt.Multiline = true; txt.BorderStyle = BorderStyle.Fixed3D; txt.ScrollBars = ScrollBars.Vertical; txt.Anchor = AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Top | AnchorStyles.Bottom; strOriginal = txt.Text; btn = new Button( ); btn.Parent = this; btn.Text = "Save"; btn.Location = new Point((ClientSize.Width / 2) - (btn.Width / 2), ClientSize.Height - (btn.Height * 2)); btn.Click += new System.EventHandler(btn_Click); btn.Anchor = AnchorStyles.Bottom; } // close for constructor static void Main( ) { Application.Run(new TextBoxTextChanged( )); } private void txt_TextChanged(object sender, EventArgs e) { if (strOriginal = = txt.Text) txt.Modified = false; else txt.Modified = true; } private void btn_Click(object sender, EventArgs e) { if (txt.Modified) { MessageBox.Show( "The contents of the TextBox have been modified. " + "This simulates saving the contents."); strOriginal = txt.Text; txt.Modified = false; } else MessageBox.Show( "The contents of the TextBox have not been modified. " + "It is not being saved."); } } // close for form class } // close form namespace

Example 12-4. TextBoxBase Modified property TextChanged event and in VB.NET (TextBoxTextChanged.vb) methods

Option Strict On imports System imports System.Drawing imports System.Windows.Forms namespace ProgrammingWinApps public class TextBoxTextChanged : inherits Form dim txt as TextBox dim btn as Button dim strOriginal as string public sub New( ) Text = "TextBox Modified and TextChanged" Size = new Size(300, 375) txt = new TextBox( ) txt.Parent = me txt.Text = "Enter text here." txt.Size = new Size(ClientSize.Width - 20, _ ClientSize.Height - 100) txt.Location = new Point(10,10) AddHandler txt.TextChanged, AddressOf txt_TextChanged txt.Multiline = true txt.BorderStyle = BorderStyle.Fixed3D txt.ScrollBars = ScrollBars.Vertical txt.Anchor = AnchorStyles.Left or AnchorStyles.Right or _ AnchorStyles.Top or AnchorStyles.Bottom strOriginal = txt.Text btn = new Button( ) btn.Parent = me btn.Text = "Save" btn.Location = new Point( _ CInt((ClientSize.Width / 2)) - CInt((btn.Width / 2)), _ ClientSize.Height - (btn.Height * 2)) AddHandler btn.Click, AddressOf btn_Click btn.Anchor = AnchorStyles.Bottom end sub ' close for constructor public shared sub Main( ) Application.Run(new TextBoxTextChanged( )) end sub private sub txt_TextChanged(ByVal sender as object, _ ByVal e as EventArgs) if strOriginal = txt.Text then txt.Modified = false else txt.Modified = true end if end sub private sub btn_Click(ByVal sender as object, _ ByVal e as EventArgs) if txt.Modified then MessageBox.Show( _ "The contents of the TextBox have been modified." + _ vbNewLine + vbNewLine + _ "This simulates saving the contents.") strOriginal = txt.Text txt.Modified = false else MessageBox.Show( _ "The contents of the TextBox have not been modified." + _ vbNewLine + vbNewLine + _ "It is not being saved.") end if end sub end class end namespace

The text box Size property is calculated from the size of the form's client area. The width is equal to the width of the client area minus 20 pixels (to allow a 10 pixel margin on either side), and the height is 100 pixels less than the client height, to allow room for the button.

txt.Size = new Size(ClientSize.Width - 20, ClientSize.Height - 100);

The text box Location property is hard coded to 10,10. The real work in dynamically positioning the controls is accomplished by setting the Anchor properties of both controls. The text box is anchored to all four sides by OR'ing together the appropriate AnchorStyles values:

txt.Anchor = AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Top | AnchorStyles.Bottom;

txt.Anchor = AnchorStyles.Left or AnchorStyles.Right or _ AnchorStyles.Top or AnchorStyles.Bottom

The button Location is also calculated from the client area size and the size of the button itself.

btn.Location = new Point((ClientSize.Width / 2) - (btn.Width / 2), ClientSize.Height - (btn.Height * 2));

The button control is anchored only to the bottom of the form.

btn.Anchor = AnchorStyles.Bottom;

Both controls have event handlers registered with them. The button handles the ubiquitous Click event, while the text box handles the TextChanged event.

txt.TextChanged += new System.EventHandler(txt_TextChanged);

AddHandler txt.TextChanged, AddressOf txt_TextChanged

The TextChanged event handler method tests to see if the current contents of the text box differ from the contents the last time the Save button was clicked (or when the form was initialized). The original contents are saved in a string variable called strOriginal, which is originally set in the constructor, and then reset every time the contents are saved.

The Modified property is automatically set to true and the TextChanged event is raised by the CLR as soon as the contents of the text box are changed by the user. Your code, however, is free to set the value to whatever is required to achieve the goals of the program.

Категории