Draw improved text on a curve in C#

[draw improved text]

This example shows how to draw improved text along a curved path. The example Draw text on a curve in C# shows how to draw text along a curved path. Unfortunately the spacing between the letters is pretty mediocre in (at least) two respects. First, the spacing is just generally too big. Second the program doesn’t add room for space characters.

The second problem is caused by the Graphics class’s MeasureString method. That method doesn’t allow room for trailing spaces because they normally don’t take up room when you draw text. For example, that method gives the strings “A” and “A ” the same width because the trailing spaces are invisible.

To draw improved text, this version of the program replaces calls to MeasureString with calls to the following MeasureText method.

// Return the size of some text adding extra room for spaces.
private SizeF MeasureText(Graphics gr, string text, Font font)
{
    SizeF size = gr.MeasureString(text, font);
    int num_spaces = text.Length - text.TrimEnd().Length;
    size.Width += font.Size * num_spaces;
    return size;
}

This code calls MeasureString as before. It then calculates the number of whitespace characters at the end of the string and adds the font’s size for each of them before returning the result.

The other thing that this example does to draw improved text is adjust the character spacing in the following code.

float text_width = MeasureText(gr, chars_that_fit, font).Width;
const float kern = 0.8f;
start_point = new PointF(
    start_point.X + dx * text_width * kern,
    start_point.Y + dy * text_width * kern);

This version of the code multiplies the space used by a piece of drawn text by a kerning value, which in this example is set to 0.8. That moves the characters a bit closer together.

In general Kerning is the process of adjusting the spacing between characters. Many fonts use kerning for specific characters or character pairs. For example, a font might move adjacent “ij” characters closer together so the j reaches a bit under the i.

The result is much better than the text drawn by the previous example. As in that version, the result will be best if you use a relatively large font and if the curves don’t change too quickly. You may also need to fiddle with the kerning value to produce the best results for your situation.


Download Example   Follow me on Twitter   RSS feed   Donate




Posted in algorithms, drawings, fonts, graphics, transformations | Tagged , , , , , , , , , , , , , , , | Leave a comment

Make a TextBox preview extender provider in C#

[example]

This example shows how to convert the TextBox preview techniques described in the last few posts into an extender provider. The previous examples showed the basic technique, but they were hard-wired to specific TextBox controls so they weren’t very flexible. An extender provider provides easy previewing and validation for any kind of TextBox.

To review the basic process of creating an extender provider, see Make an extender provider that validates required TextBoxes in C#.

This example uses an extender provider named TextBoxPreviewer. The following code shows the provider’s class declaration.

[ProvideProperty("ProvidePreview", "TextBox")]
public partial class TextBoxPreviewer : Component, IExtenderProvider
{
    ...
}

This indicates that the class implements the IExtenderProvider interface and provides a property called ProvidePreview for TextBox controls.

The extender provider intercepts keyboard events and displays a context menu for its client TextBox controls. When the user tries to change a TextBox control’s value, either by using the keyboard or the context menu, the extender provider raises an event called TextChangingDelegate to let the main program preview the new text and cancel the change if desired.

To display a context menu, the extender provider must have access to a ContextMenuStrip to display. The following code shows the provider’s constructors, which call the MakeContextMenu method to build the context menu.

public TextBoxPreviewer()
{
    InitializeComponent();

    MakeContextMenu();
}

public TextBoxPreviewer(IContainer container)
{
    container.Add(this);

    InitializeComponent();

    MakeContextMenu();
}

The following code shows how the MakeContextMenu method builds the context menu.

// This extender's context menu.
private ContextMenuStrip ctxTextBox;
private ToolStripMenuItem ctxUndo, ctxCopy, ctxCut, ctxPaste, ctxDelete;
private ToolStripSeparator ctxSeparator;

// Make the context menu.
private void MakeContextMenu()
{
    this.ctxTextBox = new ContextMenuStrip();
    this.ctxUndo = new ToolStripMenuItem();
    this.ctxSeparator = new ToolStripSeparator();
    this.ctxCopy = new ToolStripMenuItem();
    this.ctxCut = new ToolStripMenuItem();
    this.ctxPaste = new ToolStripMenuItem();
    this.ctxDelete = new ToolStripMenuItem();

    ctxTextBox.Opening += ctxTextBox_Opening;
    this.ctxTextBox.Name = "ctxTextBox";
    this.ctxTextBox.Size = new System.Drawing.Size(108, 120);
    this.ctxTextBox.Items.AddRange(new ToolStripItem[] 
        {
            this.ctxUndo,
            this.ctxSeparator,
            this.ctxCopy,
            this.ctxCut,
            this.ctxPaste,
            this.ctxDelete
        });
    this.ctxUndo.Text = "Undo";
    this.ctxUndo.Click += ctxUndo_Click;
    this.ctxCopy.Text = "Copy";
    this.ctxCopy.Click += ctxCopy_Click;
    this.ctxCut.Text = "Cut";
    this.ctxCut.Click += ctxCut_Click;
    this.ctxPaste.Text = "Paste";
    this.ctxPaste.Click += ctxPaste_Click;
    this.ctxDelete.Text = "Delete";
    this.ctxDelete.Click += ctxDelete_Click;
}

You may notice that this code uses this a lot, which is not my usual style. That’s because I copied this code from the Form1.Designer.cs file in the previous example. The code required a little rearranging because in that file it isn’t all in one place like it is here, but it was easier than building the whole menu structure from scratch.

The following code shows how the extender provider implements the IExtenderProvider interface.

// We can extend TextBoxes only.
public bool CanExtend(object extendee)
{
    return (extendee is TextBox);
}

// The list of our clients.
private List<TextBox> Clients = new List<TextBox>();

// Implement the ProvidePreview extension property.
// Return this client's ProvidePreview value.
[Category("Behavior")]
[DefaultValue(false)]
public bool GetProvidePreview(TextBox client)
{
    // Return true if the client is in the Clients list.
    return Clients.Contains(client);
}

// Set this control's ProvidePreview value.
[Category("Behavior")]
[DefaultValue(false)]
public void SetProvidePreview(TextBox client, bool provide_preview)
{
    if (provide_preview)
    {
        // Add the client to the list.
        if (!Clients.Contains(client))
        {
            // Add the client to the list of clients.
            Clients.Add(client);

            // Add event handlers.
            client.KeyDown += txt_KeyDown;
            client.KeyPress += txt_KeyPress;

            // Attach the ContextMenuStrip.
            client.ContextMenuStrip = ctxTextBox;
        }
    }
    else
    {
        // Remove the client from the list.
        if (Clients.Contains(client))
        {
            // Remove the client from the list of clients.
            Clients.Remove(client);

            // Remove event handlers.
            client.KeyDown -= txt_KeyDown;
            client.KeyPress -= txt_KeyPress;

            // Detach the ContextMenuStrip.
            client.ContextMenuStrip = null;
        }
    }
}

The CanExtend method returns true if the provider can extend a particular object. In this example, the provider can extend an object if that object is a TextBox.

The extender provider stores references to the TextBox controls that it serves in a List<TextBox> named Clients. The GetProvidePreview and SetProvidePreview methods get and set the ProvidePreview property values.

This is one of the stranger features of extender providers. The methods that get and set values for a property named Xxx must be named GetXxx and SetXxx. That’s how the program identifies them.

The GetProvidePreview method simply returns true if the TextBox in question is in the Clients list.

The SetProvidePreview method adds or removes a TextBox from the Clients list.

When it adds a TextBox to the list, it registers event handlers to catch the client’s KeyDown and KeyPress events. It also sets the TextBox control’s ContextMenuStrip property to the ContextMenuStrip that the MakeContextMenu method created. If the user later right-clicks the TextBox, this is the menu that is displayed.

When it removes a TextBox from the client list, the SetProvidePreview method unregisters the TextBox controls KeyDown and KeyPress event handlers and sets its ContextMenuStrip property to null.

The end goal of the extender provider is to raise a TextChanging event that the main program can catch to preview TextBox changes and to reject those changes if necessary. The following code shows how the provider declares this event.

// The event we raise when the TextBox's value is about to change.
public delegate void TextChangingDelegate(TextBox text_box,
    string new_text, ref bool cancel);
public event TextChangingDelegate TextChanging;

This code creates a delegate that represents a method that takes as parameters a TextBox, the new value that the TextBox will have if the changes are accepted, and a Boolean that the main program can set to true to cancel the changes.

In the previous examples, the ShouldCancelTextBoxEvent method called a method named ValueIsValid to decide whether the new value made sense. This example uses the following version of ShouldCancelTextBoxEvent.

private bool ShouldCancelTextBoxEvent(
    TextBox txt, EditType edit_type, char new_char, bool assign_value)
{
    // Get the new value.
    int selection_start;
    string new_value = GetNewTextBoxValue(txt, edit_type,
        new_char, out selection_start);

    // Raise the TextChanging event to see if the value is valid.
    bool cancel = false;
    TextChanging(txt, new_value, ref cancel);

    if (cancel)
    {
        // The new value is invalid. Discard it.
        // (Let the main program decide whether to beep.)
        return true;
    }
    else
    {
        // It's okay. Accept it.
        if (assign_value)
        {
            txt.Text = new_value;
            txt.Select(selection_start, 0);
            return true;
        }
        return false;
    }
}

In this version, the code raises the TextChanging event. The main program tests the new value and sets the cancel parameter to indicate whether the extender provider should cancel the event. After the TextChanging event returns, the ShouldCancelTextBoxEvent allows or stops the event as appropriate.

The final parts of the extender provider deal with handling events when the user presses keys or selects a context menu command. These pieces of code are similar to those used by the previous examples except it’s a little harder to figure out which controls were involved in raising the event.

For example, the following code shows the KeyPress event handler that executes when the user presses a key on a client TextBox.

// Handle normal characters.
private void txt_KeyPress(object sender, KeyPressEventArgs e)
{
    TextBox txt = sender as TextBox;

    // Do nothing for special characters.
    if ((e.KeyChar < ' ') || (e.KeyChar > '~')) return;

    // See if the change is valid.
    e.Handled = ShouldCancelTextBoxEvent(
        txt, EditType.NewCharacter, e.KeyChar, false);
}

In the previous examples, this code used a specific TextBox such as txtInteger or txtFloat. This version uses the sender parameter to figure out which TextBox raised the KeyPress event. After the method figures out which TextBox is involved, the rest of the code is the same as before.

The code that responds to context menu events in this example is also a little different from the code used in the previous examples. Those examples used code hard-wired to work with specific TextBox controls. In this example, the context menu might be associated with several controls so the code must figure out which TextBox to use.

Fortunately the ContextMenuStrip component’s SourceControl property tells which control is displaying the context menu. The following code shows how the context menu’s Opening event handler enables the appropriate context menu items when the user right-clicks on a TextBox.

// Enable appropriate context menu items.
private void ctxTextBox_Opening(object sender, CancelEventArgs e)
{
    ContextMenuStrip ctx = sender as ContextMenuStrip;
    TextBox txt = ctx.SourceControl as TextBox;

    // Undo is enabled if the TextBox has something it can undo.
    ctxUndo.Enabled = txt.CanUndo;

    // Copy and Cut are enabled if anything is selected.
    ctxCopy.Enabled = (txt.SelectionLength > 0);
    ctxCut.Enabled = (txt.SelectionLength > 0);

    // Delete is enabled if anything is selected or there
    // is a character after the insertion point to delete.
    ctxDelete.Enabled =
        ((txt.SelectionLength > 0) ||
         (txt.SelectionStart < txt.Text.Length));

    // Paste is enabled if the clipboard contains text.
    ctxPaste.Enabled = Clipboard.ContainsText();
}

First the code gets the ContextMenuStrip object from the event’s sender parameter. It then uses that object’s SourceControl property to get the TextBox that the user right-clicked to open the context menu. After this point the code is similar to the code used in the previous examples.

Just as the context menu’s code must figure out which TextBox was right-clicked, so too must the context menu item event handlers. The following code shows how the Copy menu item’s Click event handler does this.

private void ctxCopy_Click(object sender, EventArgs e)
{
    TextBox txt = ctxTextBox.SourceControl as TextBox;
    Clipboard.Clear();
    Clipboard.SetText(txt.SelectedText);
}

This code uses the extender provider’s ctxTextBox variable to get the ContextMenuStrip. It then uses that object’s SourceControl property to get the TextBox that was right-clicked. After that the code is similar to the previous version.

Download the example to see how the rest of the extender provider’s code works. It’s very similar to the code used in the previous example so it isn’t repeated here.

With all of the extender provider pieces in place, all that remains is the main program. After building the project, I added a TextBoxPreviewer to the form at design time. I then set the ProvidePreview on textBoxPreviewer1 property for the two TextBox controls to true so the provider would serve them.

Next I used the Properties window to create a TextChanging event for the extender provider and added code to make it look like the following.

// Validate a TextBox's new value.
private void textBoxPreviewer1_TextChanging(
    TextBox text_box, string new_text, ref bool cancel)
{
    // If the value is blank, just return and let it happen.
    if (new_text.Length == 0) return;

    // If the value doesn't end in a digit, add a 0.
    if (!char.IsDigit(new_text, new_text.Length - 1)) new_text += '0';

    // See which TextBox this is.
    if (text_box == txtInteger)
    {
        // Make sure this looks like an integer.
        int test_value;
        cancel = !int.TryParse(new_text, out test_value);
    }
    else
    {
        // Make sure this looks like a float.
        float test_value;
        cancel = !float.TryParse(new_text, out test_value);
    }

    // If we set cancel to true, beep.
    if (cancel) System.Media.SystemSounds.Beep.Play();
}

First the code checks the new text’s length and, if the text is blank, the code returns immediately. That leaves parameter cancel set to its initial value false so the event is not canceled and the blank value is allowed.

Next, if the new text doesn’t end in a digit, the code appends the character 0. That turns incomplete values such as “-” and “1.2e” into valid integers and floats as in “-0” and “1.2e0” respectively.

Depending on which TextBox is being modified, the code uses int.TryParse or float.TryParse to see if the new value is valid and sets the cancel parameter accordingly. Finally, if cancel is true, the code plays the beep sound.


Download Example   Follow me on Twitter   RSS feed   Donate




Posted in controls, extensions, user interface | Tagged , , , , , , , , , , , , , , | Leave a comment

Make the user enter a float in C#

[float]

The posts Preview TextBox changes in C# and Preview TextBox changes in C#, Part 2 show how you can make a TextBox accept only integers. This example is similar except it makes a TextBox only accept floats.

This change is remarkably simple. (Almost like I planned it that way, huh?)

The key method that determines whether a new TextBox value is valid is the ValueIsValid method. In the previous examples, that method returns true if the new value is a valid integer or the beginning of one as in – or +. The new version of this method returns true if the value looks like a float or the beginning of one.

The code can use float.TryParse to determine if the test value is actually a float but that won’t always work if the user is in the process of typing the new value. For example, suppose the user wants to type -9e-8. This is a valid float but at some point the user will have typed -9e-, which is not a valid value.

You could try to build a state machine that determines whether the test string could be the start of a valid float value, but you would need to handle a bunch of cases including exponential notation in various partially complete stages in addition to fixed-point values values such as -3.14.

Fortunately there is an easier method. If the test string ends in a non-digit, then just add a digit to the end and see if the result is a valid float. For example, -9e- becomes -9e-0, which is weird but valid. Similarly – and + become -0 and +0, which are also weird but valid.

The following version of ValueIsValid uses this approach.

// Return true if the value is a valid integer.
private bool ValueIsValid(string text_value)
{
    // Do not allow spaces.
    if (text_value.Contains(' ')) return false;

    // Allow a blank value.
    if (text_value.Length == 0) return true;

    // If the text doesn't end in a digit, add a digit
    // to see if it is a valid prefix of a float.
    if (!char.IsDigit(text_value, text_value.Length - 1))
        text_value += "0";

    // See if the text parses.
    float test_value;
    return float.TryParse(text_value, out test_value);
}

This method rejects the value if it contains space characters and it accepts a blank value.

Next if the value doesn’t end in a digit, the code appends a 0. The method then uses float.TryParse to see if the result is a valid float.

Note that the code still allows some slightly confusing values. For example, 1e100 is not a valid float because it is too large (it is a valid double) but 1e-100 is valid because it is essentially 0.0.

The previous examples let the user enter only integers and this example lets the user enter only floats. You can easily modify the ValueIsValid method to make the user enter other data types such as byte, long, ulong, double, decimal, and others. With a little work, you could even restrict the user to Boolean values such as True, False, Yes, and No.

This example and the earlier ones use code that is hard-wired to a particular TextBox. My next post explains how to make the code easier to use more easily with multiple TextBox controls.


Download Example   Follow me on Twitter   RSS feed   Donate




Posted in controls, user interface | Tagged , , , , , , , , , , , , , , , | Leave a comment

Preview TextBox changes in C#, Part 2

Part 1 of this post explained how the example previews changes made by keyboard events. It then accepts or cancels the changes depending on whether the TextBox control’s new value is a valid integer (or the start of one as in ” “, “-“, or “+”). This post explains how the example handles the TextBox control’s context menu.

By default the TextBox provides a context menu that provides a bunch of commands including Undo, Copy, Cut, Paste, and Delete. All of these can change the control’s value.

Although you cannot intercept the TextBox control’s context menu, you can disable it or replace it with a context menu of your own. To disable the context menu, see Remove a TextBox control’s context menu in C#.

This post uses the second approach.

At design time, I added a ContextMenuStrip to the form and gave it the menu items Undo, Copy, Cut, Paste, and Delete as shown in the picture. You could assign the menu to the TextBox control’s ContextMenuStrip property at run time, but in order to simplify design time setup I made that assignment in the following Form_Load event handler.

// Attach the TextBox's ContextMenuStrip.
private void Form1_Load(object sender, EventArgs e)
{
    txtInteger.ContextMenuStrip = ctxTextBox;
}

The following code shows how the menu’s commands work.

// Make context menu items to do the
// same work as the control characters.
private void ctxUndo_Click(object sender, EventArgs e)
{
    txtInteger.Undo();
}
private void ctxCopy_Click(object sender, EventArgs e)
{
    Clipboard.Clear();
    Clipboard.SetText(txtInteger.SelectedText);
}
private void ctxCut_Click(object sender, EventArgs e)
{
    ShouldCancelTextBoxEvent(txtInteger,
        EditType.Cut, ' ', true);
}
private void ctxPaste_Click(object sender, EventArgs e)
{
    ShouldCancelTextBoxEvent(txtInteger,
        EditType.Paste, ' ', true);
}
private void ctxDelete_Click(object sender, EventArgs e)
{
    ShouldCancelTextBoxEvent(txtInteger,
        EditType.Delete, ' ', true);
}

These are fairly straightforward. Several of them use the ShouldCancelTextBoxEvent method described in the previous post. That method validates the change and beeps if a change is invalid.

Notice that final argument passed to ShouldCancelTextBoxEvent in this code is true. That value tells ShouldCancelTextBoxEvent that it should update the TextBox control’s value if the change is valid so the menu commands can do their jobs. (Recall that when the keyboard event handlers call ShouldCancelTextBoxEvent, they set this final argument to false so the keyboard events can make changes if they are valid. There are no similar keyboard events when the context menu commands execute.)

The only remaining piece to this example is the following event handler that executes when the context menu is opening.

// Enable appropriate context menu items.
private void ctxTextBox_Opening(object sender, CancelEventArgs e)
{
    ctxUndo.Enabled = txtInteger.CanUndo;

    // Copy and Cut are enabled if anything is selected.
    ctxCopy.Enabled = (txtInteger.SelectionLength > 0);
    ctxCut.Enabled = (txtInteger.SelectionLength > 0);

    // Delete is enabled if anything is selected or there
    // is a character after the insertion point to delete.
    ctxDelete.Enabled =
        ((txtInteger.SelectionLength > 0) ||
         (txtInteger.SelectionStart < txtInteger.Text.Length));

    // Paste is enabled if the clipboard contains text.
    ctxPaste.Enabled = Clipboard.ContainsText();
}

This code enables and disables the appropriate context menu commands depending on what is selected in the TextBox. For example, the Copy and Cut commands are enabled only if some text is selected in the TextBox.

The only command that is a bit uncertain here is Paste because it might end up creating an invalid value, which would then be prohibited. For example, if the clipboard contains the text "ten" and you try to paste it into the TextBox, the ShouldCancelTextBoxEvent method prevents it and beeps.

If you wanted to you could make the ctxTextBox_Opening event handler evaulate the potential result of the paste and disable that command if the result would be invalid. I decided it was better to let the user try to paste and get a beep instead of wondering why Paste was disabled when there was text in the clipboard. This version is also simpler, but you can change it if you like.

Note that you could have a similar issue with the Cut command, at least if you're looking for floating point values instead of integers. For example, suppose the TextBox holds the value -1.2E-10 and the "1.2E" is highlighted. In that case using Cut would make the value "--10" and that's not a valid floating point value.

This example makes the user enter only integers in a TextBox. My next post will show how to make the user enter only floating point values.


Download Example   Follow me on Twitter   RSS feed   Donate




Posted in controls, user interface | Tagged , , , , , , , , , , , , | Leave a comment

Preview TextBox changes in C#

[preview]

One of the more annoying omissions from the Windows Forms controls is a way to preview changes to a TextBox before they occur. For example, suppose you want the user to enter a floating point value in a TextBox. The TextBox class has a TextChanged event that lets you decide whether the user has entered something valid, but at that point it’s too late to stop the user from entering gibberish. You can try catching the KeyPress event and deciding whether the new key should be allowed, but that doesn’t catch all of the ways the user might change the TextBox control’s value. Those ways include:

  • Typing a normal character such as X or 7
  • Pressing Delete to delete the selected text or the character after the insertion point if nothing is selected
  • Pressing Backspace to delete the selected text or the character before the insertion point if nothing is selected
  • Pressing Ctrl+X to cut the selected text into the clipboard
  • Pressing Ctrl+V to paste whatever is in the clipboard
  • Pressing Ctrl+Z to undo the most recent change
  • Pressing Shift+Delete to cut the selected text into the clipboard
  • Pressing Shift+Insert to paste whatever is in the clipboard
  • Right-clicking and selecting Undo from the context menu
  • Right-clicking and selecting Cut from the context menu
  • Right-clicking and selecting Paste from the context menu
  • Right-clicking and selecting Delete from the context menu

I don’t know why Microsoft doesn’t simply add a TextChanging event to the TextBox control that lets you preview and cancel changes to the text, but this has been a problem with the TextBox control since before .NET was invented so it’s unlikely to change any time soon. I’ve seen lots of posts on the Internet that try to deal with this problem, but the ones I’ve seen all miss one or more of the cases listed above. (Most forget about Ctrl+V and using the context menu.)

Here’s my attempt at solving this problem. This example works explicitly with the txtInteger TextBox.

This post only deals with keyboard events that change the TextBox control’s value. My next post will explain how to handle the context menu, a much simpler topic.

The idea is to catch the events where changes to the text might occur, determine what value the TextBox will hold if the change is allowed, and then either accept of cancel the change.

One of the key methods used by this example is GetNewTextBoxValue. This method, which determines what value the TextBox will hold if a particular change is allowed, is shown in the following code.

// Possible change types for the TextBox's value.
private enum EditType
{
    NewCharacter,
    Cut,
    Paste,
    Delete,
    Backspace,
}

// Return the value that the TextBox will have if this change is allowed.
private string GetNewTextBoxValue(TextBox txt, EditType edit_type,
    char new_char, out int selection_start)
{
    // Get the pieces of the current text.
    selection_start = txt.SelectionStart;
    string current_text = txt.Text;
    string left_text = current_text.Substring(0, selection_start);
    string selected_text = txt.SelectedText;
    string right_text = 
        current_text.Substring(selection_start + txt.SelectionLength);

    // Compose the result.
    string result = "";
    switch (edit_type)
    {
        case EditType.NewCharacter:
            result = left_text + new_char + right_text;
            selection_start++;
            break;
        case EditType.Cut:
            result = left_text + right_text;
            if (txt.SelectionLength > 0)
            {
                Clipboard.Clear();
                Clipboard.SetText(selected_text);
            }
            break;
        case EditType.Paste:
            if (Clipboard.ContainsText())
            {
                selected_text = Clipboard.GetText();
                selection_start += selected_text.Length;
            }
            result = left_text + selected_text + right_text;
            break;
        case EditType.Delete:
            if (selected_text.Length == 0)
            {
                // Remove the following character.
                if (right_text.Length > 0)
                    right_text = right_text.Substring(1);
            }
            else
            {
                // Remove the selected text.
                selected_text = "";
            }
            result = left_text + selected_text + right_text;
            break;
        case EditType.Backspace:
            if (selected_text.Length == 0)
            {
                // Remove the previous character.
                if (left_text.Length > 0)
                {
                    left_text = left_text.Substring(0, selection_start - 1);
                    selection_start--;
                }
            }
            else
            {
                // Remove the selected text.
                selected_text = "";
            }
            result = left_text + selected_text + right_text;
            break;
    }

    // Return the result.
    return result;
}

The EditType enumeration lists the possible types of changes that the TextBox control’s value might experience.

The GetNewTextBoxValue method takes as parameters the TextBox, the type of change being considered, a new character to add to the text (if the user pressed a normal key such as H or 3), and an integer where the method can indicate the TextBox control’s new SelectionStart value if the change is allowed. The method returns the TextBox control’s new value.

The method starts by breaking the current text into three pieces: the text to the left of the current selection, the current selection, and the text to the right of the current selection.

The code then uses a switch statement to compose the result that will occur for the different EditType values. You can walk through the code (and possibly experiment with a TextBox at run time) to verify that the code builds the correct result.

Notice that the Cut code copies the selected text to the clipboard. This happens even if the resulting TextBox value is invalid so the change isn’t allowed. I figured the user would want the text copied to the clipboard even if you couldn’t legally remove the selected text. You can change this if you like.

Notice also that the behavior of the Delete and Backspace changes depends on whether the TextBox currently has any text selected.

After building the result string, the method returns it.

The ShouldCancelTextBoxEvent method shown in the following code calls GetNewTextBoxValue and then decides whether the event that started the change should be canceled.

// Compose the TextBox's new value and call ValueIsValid
// to see if it is valid.
//
// If the change is invalid, beep and return true to indicate
// the event should be canceled.
//
// If the change is valid and assign_value is true, assign the
// value to the TextBox and then return true to indicate that
// the event is no longer needed.
//
// If the change is valid and assign_value is false, then return
// false to indicate that the event still needs to be handled.
private bool ShouldCancelTextBoxEvent(
    TextBox txt, EditType edit_type, char new_char, bool assign_value)
{
    // Get the new value.
    int selection_start;
    string new_value =
        GetNewTextBoxValue(txt, edit_type, new_char, out selection_start);

    // See if it's a valid value.
    if (ValueIsValid(new_value))
    {
        // It's okay. Accept it.
        if (assign_value)
        {
            txt.Text = new_value;
            txt.Select(selection_start, 0);
            return true;
        }
        return false;
    }
    else
    {
        // The new value is invalid. Complain.
        System.Media.SystemSounds.Beep.Play();
        return true;
    }
}

This code starts by calling GetNewTextBoxValue. It then calls ValueIsValid (described shortly) to see if the new value should be allowed. I moved all of the testing logic into ValueIsValid so you can modify it to handle other data types such as floats or some specialized format such as phone numbers. (The ValueIsValid method is basically the equivalent of the missing TextChanging event.)

The method’s assign_value parameter indicates whether ShouldCancelTextBoxEvent should set the TextBox control’s value. (You’ll understand how this is used when you read about the difference between the user pressing a key such as Ctrl+V and using the context menu to paste text.)

There are three possibilities:

  1. ValueIsValid returns true and assign_value is true. In this case the code sets the TextBox control’s value directly and returns true to indicate that the event that started the change should be discarded with no further action. Changes made in this way do not become part of the TextBox control’s Undo buffer so they cannot be undone.
  2. ValueIsValid returns true and assign_value is false. In this case the code returns false to indicate that the event that started the change should be allowed to proceed and update the TextBox control’s value. Changes made in this way become part of the TextBox control’s Undo buffer so they can be undone.
  3. ValueIsValid returns false. In this case the method returns true to indicate that the change is invalid and the event that started it should be discarded.

The following code shows the ValueIsValid method that decides whether the change should be allowed.

// Return true if the value is a valid integer.
private bool ValueIsValid(string text_value)
{
    // Do not allow spaces.
    if (text_value.Contains(' ')) return false;

    // Allow a blank string, -, and +.
    if ((text_value.Length == 0) ||
        (text_value == "-") ||
        (text_value == "+")) return true;

    // See if the text parses.
    int test_value;
    return int.TryParse(text_value, out test_value);
}

This version of ValueIsValid tries to allow only integers. First the code disallows any value that contains a space. Although a value such as ” 17 ” can be parsed as an integer, it’s not really the intent to let the user enter values that contain spaces. This also means the code doesn’t need to worry about values such as ” -” that aren’t integers but might be the start of an integer.

Next the method allows a blank value, a -, or a + because all of those might occur when the user is starting to enter an integer.

If the value passes those tests, the code simply uses int.TryParse to attempt to convert it into an int. It returns true if TryParse returns true, indicating that the value can be parsed as an integer.

That’s the end of the code that evaluates values and decides whether they make sense. The rest of the code shown here consists of the event handlers that use that code.

When the user presses a key, the following KeyPress event handler executes to see if that key should be allowed.

// Handle normal characters.
private void txtInteger_KeyPress(object sender, KeyPressEventArgs e)
{
    // Do nothing for special characters.
    if ((e.KeyChar < ' ') || (e.KeyChar > '~')) return;

    // See if the change is valid.
    e.Handled = ShouldCancelTextBoxEvent(
        txtInteger, EditType.NewCharacter, e.KeyChar, false);
}

If the key isn’t a normal visible key, the event handler just returns, allowing the key to be processed normally. Special keys such as Ctrl+X and Backspace are processed by the KeyDown event handler described shortly.

For visible keys, the code calls ShouldCancelTextBoxEvent to see if the new value will be valid. If ShouldCancelTextBoxEvent returns true, the KeyPress event handler flags the event as handled so it does not continue and it is ignored by the TextBox.

Notice that the final parameter passed to ShouldCancelTextBoxEvent is false, indicating that it should not assign the new value to the TextBox if it is valid. If the value is valid, ShouldCancelTextBoxEvent returns false and the event handler sets e.Handled to false so the key is processed normally by the TextBox.

The KeyPress event handler deals with normal characters. The program uses the following KeyDown event handler to deal with control sequences such as Ctrl+X and other special keys such as Backspace and Delete.

// Handle Ctrl+A, Ctrl+X, Ctrl+V, Shift+Insert,
// Shift+Delete, Delete, and Backspace.
private void txtInteger_KeyDown(object sender, KeyEventArgs e)
{
    // Look for the necessary key combinations.
    bool cancel_event;
    if (e.Control && (e.KeyCode == Keys.A))
    {
        // Handle this one just for convenience.
        txtInteger.Select(0, txtInteger.Text.Length);
        cancel_event = true;
    }
    else if (e.Control && (e.KeyCode == Keys.X))
    {
        cancel_event =
            ShouldCancelTextBoxEvent(txtInteger,
                EditType.Cut, ' ', false);
    }
    else if (e.Control && (e.KeyCode == Keys.V))
    {
        cancel_event =
            ShouldCancelTextBoxEvent(txtInteger,
                EditType.Paste, ' ', false);
    }
    else if (e.Shift && (e.KeyCode == Keys.Insert))
    {
        cancel_event = ShouldCancelTextBoxEvent(
            txtInteger, EditType.Paste, ' ', false);
    }
    else if (e.Shift && (e.KeyCode == Keys.Delete))
    {
        cancel_event = ShouldCancelTextBoxEvent(
            txtInteger, EditType.Cut, ' ', false);
    }
    else if (e.KeyCode == Keys.Delete)
    {
        cancel_event =
            ShouldCancelTextBoxEvent(txtInteger,
                EditType.Delete, ' ', false);
    }
    else if (e.KeyCode == Keys.Back)
    {
        cancel_event =
            ShouldCancelTextBoxEvent(txtInteger,
                EditType.Backspace, ' ', false);
    }
    else
    {
        // We didn't handle the event.
        // Let the event proceed normally.
        cancel_event = false;
    }

    // If we handled it, stop the event.
    if (cancel_event)
    {
        e.Handled = true;
        e.SuppressKeyPress = true;
    }
}

To look for control sequences, the code checks the e.Control parameter to see if the Ctrl key is pressed and it uses the e.KeyCode parameter to see if a particular key is also pressed. For example, the user is pressing Ctrl+X if e.Control is true and e.KeyCode is Keys.X. Notice that the code doesn’t check other special keys such as Shift and Alt so, for example, Ctrl+V and Ctrl+Shift+V work the same way. This is consistent with the way the TextBox normally behaves.

The code explicitly checks for each of the Ctrl+A, Ctrl+X, Ctrl+V, Delete, and Backspace keys. (Looking for Ctrl+A isn’t really necessary, but I added it for convenience to let the user select all of the TextBox control’s text. If you want to use Ctrl+A for some other purpose, comment out this code.)

In each case the KeyDown event handler calls ShouldCancelTextBoxEvent passing it the appropriate EditType value. The final parameter to ShouldCancelTextBoxEvent is false so that method doesn’t set the TextBox control’s value.

The ShouldCancelTextBoxEvent method returns true if the change is invalid. In that case, the KeyDown event handler suppresses the key stroke so it is not processed by the TextBox.

Whew! That’s all the code this example uses to preview key board events. My next post will explain how to handle the context menu, which is a lot easier.

This example uses code hard wired to the txtInteger TextBox control. Later posts will make this solution easier to apply to other controls.

This is a pretty complicate example. I think I’ve covered everything but please post a comment below if you find situations that it doesn’t handle properly.


Due to some weirdness on the server, I needed to destroy and re-create this post and unfortunately that removed the comments. Here’s a summary.

On 10/24/2012 Markus Goelzner wrote:

I’ve done that for the Delphi VCL long ago and as you mention it, it is likely to miss a case. For example Shift+Insert is a shortcut to paste, Shift+Delete is shortcut to cut. You should process WM_PASTE, WM_CUT and maybe WM_CLEAR.

On 10/25/2012 I replied:

Thanks for mentioning this. I didn’t know about Shift+Insert and Shift+Delete. I’ll modify the example to cover them. (I wonder if there aren’t too many ways to do these things. Do we really need so many?)

Then on 10/26/2012 I added:

I’ve added code to check for these key sequences. Please let me know if you find others that aren’t handled correctly.

Looking for the messages such as WM_CUT and WM_PASTE would probably be better in some ways because it lets you treat these at a higher level. It doesn’t really matter whether the user presses Ctrl+X or Shift+Delete to cut. However it would require subclassing and I’m trying to avoid making a new TextBox subclass.


Download Example   Follow me on Twitter   RSS feed   Donate




Posted in controls, user interface | Tagged , , , , , , , , , , , , | 2 Comments

Use Array methods in C#

[Array methods]

The example Sort and search arrays in C# shows how to use two useful Array methods: Sort and BinarySearch. This example demonstrates some other useful Array methods.

For simplicity, this example uses arrays of characters to demonstrate the Array methods, but those methods work with any kind of array. You could perform some of these operations on the characters more easily if you treat them as strings, but the point of this example is to show how the Array methods work.

Enter a string in the Items RichTextBox at the top of the form and a search character in the Test Item text box. Then click Go to make the program use the following code to demonstrate the Array methods.

private void btnGo_Click(object sender, EventArgs e)
{
    // Clear any previous formatting.
    rchItems.Select(0, rchItems.Text.Length);
    rchItems.SelectionColor = Color.Black;
    rchItems.SelectionBackColor = Color.White;

    // Array.Reverse.
    char[] items = rchItems.Text.ToCharArray();
    Array.Reverse(items);
    txtReverse.Text = new string(items);

    // Array.Sort.
    items = rchItems.Text.ToCharArray();
    Array.Sort(items);
    txtSort.Text = new string(items);

    // Array.IndexOf.
    char test_char = txtTestChar.Text[0];
    items = rchItems.Text.ToCharArray();
    int index_of = Array.IndexOf(items, test_char);
    txtIndexOf.Text = index_of.ToString();
    if (index_of >= 0)
    {
        rchItems.Select(index_of, 1);
        rchItems.SelectionColor = Color.Red;
    }

    // Array.LastIndexOf.
    int last_index_of = Array.LastIndexOf(items, test_char);
    txtLastIndexOf.Text = last_index_of.ToString();
    if (last_index_of >= 0)
    {
        rchItems.Select(last_index_of, 1);
        rchItems.SelectionBackColor = Color.LightBlue;
    }
    rchItems.Select(0, 0);

    // Copy Equals.
    char[] copy = new char[items.Length];
    Array.Copy(items, copy, items.Length);
    txtCopyEquals.Text = Array.Equals(items, copy).ToString();

    // Clone Equals.
    char[] clone = (char[])items.Clone();
    txtCloneEquals.Text = Array.Equals(items, clone).ToString();

    // Two arrays set to indicate the same memory location.
    char[] reference = items;
    txtRefEquals.Text = Array.Equals(items, reference).ToString();

    // Array.Resize.
    int initial_length = items.Length;
    Array.Resize(ref items, 2 * initial_length);
    Array.Copy(items, 0, items, initial_length, initial_length);
    rchLarger.Text = new string(items);

    // Array.Resize.
    Array.Resize(ref items, initial_length / 2);
    rchSmaller.Text = new string(items);
}

The code first removes any formatting from the Items RichTextBox in case you’ve clicked Go before.

The first Array method the code demonstrates is Reverse. The code gets the text that you entered, converts it into an array of char, and stores it in the array items. It then calls Array.Reverse to reverse the items in the array. Notice that this method, and many other Array methods, does not return a value. Instead those methods perform their operations on the array itself. That means if you want to preserve the original array, you must copy it before you call Reverse or the other methods. This example recreates the items array as needed by reading it from the Items RichTextBox.

After it calls Array.Reverse, the code uses the reversed array to initialize a new string and displays it. The result is the original string with its letters arranged backwards.

Next the code uses the Array.Sort method to sort the characters in the items array. It then displays the result.

The code then uses the Array.IndexOf method to find the test character in the items array. This is relatively simple and you’ve probably used IndexOf with strings. This is exactly the same except Array.IndexOf can locate any kind of item in an array. For example, if you have an array of Person objects, it can locate a particular object. Note that the objects must be the same Person objects, not two different objects that happen to have the same property values. The Array.IndexOf method uses reference equality not value equality.

After it finds the index of the target character, the code displays its position and highlights the character in the original string in red.

Similarly the code uses Array.LastIndexOf to find the last instance of the character, displays its location, and highlights it with a light blue background.

Next the code demonstrates the Array.Equals method three times. First it uses Array.Copy to make a copy of the array and then uses Array.Equals to see if the two are equal. Even though both arrays contain the same items, they are not equal (as you can see in the picture). (One way to understand this is to ask yourself what would happen if you set the first entry in one array to X. The entry in that array would change but the entry in the other array would not.)

The code repeats this test with a cloned version of the array. Again the two arrays hold the same items but they are not the same arrays so Array.Equals returns false.

Next the program creates a new variable named reference to refer to an array of char and sets it equal to the items array. Now both reference and items refer to the same array, so Array.Equals returns true. (Now if you set reference[0] to X, items[0] also becomes X because they are two names for the same array.)

The program finishes by resizing the items array twice. First it doubles the length of the array and then uses Array.Copy to copy the first half of the newly resized array (where the original items are) into the second half. It then displays the larger array’s values.

The code then resizes the array so it has half its original size and displays its contents.

In both resize operations, Array.Resize copies whatever items will fit from the original array to the newly resized array. This lets you make an array expandable just like a List does (although probably less efficiently).


Download Example   Follow me on Twitter   RSS feed   Donate




Posted in arrays, classes | Tagged , , , , , , , , , , , , , | Leave a comment

Puzzle: Zero rows and columns in an array in C#

[puzzle]

This puzzle is given as a coding example in the book Cracking the Coding Interview by Gayle Laakmann McDowell. The puzzle is:

Write an algorithm such that if an element in an M x N matrix is 0, its entire row and column are set to 0.

In other words, given an array, if an entry is 0, set all of the entries for its row and column to 0. A few rules:

  • Make sure the program is correct. Don’t get carried away and fill the whole array with 0s.
  • Be efficient. Don’t use more time or extra space than necessary.
  • Don’t use tools that are specific to the .NET Framework or C#. For example, Array.Copy, MemCopy, RtlMoveMemory, and other tools can help you copy an array quickly. Don’t use any of those.

I’ll post the book’s solution (converted from Java into C#) and my solution next week.


Follow me on Twitter   RSS feed   Donate




Posted in arrays, games | Tagged , , , , , , , , | Leave a comment

Compare performance looping over DateTime and integer variables in C#

[performance]

The example Find Friday the Thirteenths in C# doesn’t really worry about performance. It uses integers to loop over the dates within a range. For every year between the start and end dates, the code considers the 13th of each month in that year. It then ignores any months before the start date and ends the loop if it finds a date after the end date. It’s a short loop so performance doesn’t matter much.

This method works and is reasonably fast but it makes sense to wonder if it would be more efficient to not need to test every date to see if it is before the start date or after the end date.

The following code shows how this example compares the performance of two types of loops over a range of dates.

// Compare two methods for looping over the dates.
private void btnGo_Click(object sender, EventArgs e)
{
    txtDateTimeLoop.Clear();
    txtIntLoop.Clear();

    // Test variables.
    DateTime loop_start_time;
    TimeSpan elapsed;
    bool test_bool;

    // Get the start and end date components.
    DateTime start_date = dtpStart.Value.Date;
    DateTime end_date = dtpEnd.Value.Date;
    int start_year = start_date.Year;
    int end_year = end_date.Year;

    // *** Loop by using int variables. ***
    // Loop over the selected years.
    loop_start_time = DateTime.Now;
    for (int year = start_year; year <= end_year; year++)
    {
        // Loop over the months in the year.
        for (int month = 1; month <= 12; month++)
        {
            // See if this month's 13th is a Friday.
            DateTime test_date = new DateTime(year, month, 13);

            // If we haven't reached the start date, skip this one.
            if (test_date < start_date) continue;

            // If we've passed the end date, stop looping.
            if (test_date > end_date) break;

            // See if this is a Friday.
            test_bool = (test_date.DayOfWeek == DayOfWeek.Friday);
        }
    }
    elapsed = DateTime.Now - loop_start_time;
    txtIntLoop.Text = elapsed.TotalSeconds.ToString("0.0000") + " secs";

    // *** Loop by using a DateTime variable. ***
    // Find the first 13th date >= start_date.
    DateTime loop_start =
        new DateTime(start_year, start_date.Month, 13);
    if (loop_start < start_date) loop_start.AddMonths(1);

    // Find the last 13th date <= end_date.
    DateTime loop_end = new DateTime(end_year, end_date.Month, 13);
    if (loop_end > end_date) loop_end.AddMonths(-1);

    // Time the first loop.
    loop_start_time = DateTime.Now;
    for (DateTime loop_date = loop_start;
        loop_date < loop_end;
        loop_date = loop_date.AddMonths(1))
    {
        test_bool = (loop_date.DayOfWeek == DayOfWeek.Friday);
    }
    elapsed = DateTime.Now - loop_start_time;
    txtDateTimeLoop.Text =
        elapsed.TotalSeconds.ToString("0.0000") + " secs";
}

The code creates some variables to record start and elapsed times and then gets some information about the selected start and end dates. It then uses integers to represent the years and months in the date range and ignores any dates that fall outside of the range. See the previous example for more information on how that loop works.

Next the code calculates the first 13th of the month greater than or equal to the start date and the last 13th of the month before or equal to the end date. It then uses a DateTime variable to loop over the dates between the start 13th and the end 13th. After each iteration, the loop’s iteration section uses the DateTime variable’s AddMonth method to add 1 month to the variable, making it move to the 13th of the following month.

You can see from the picture that the code that uses integers for looping has better perrformance than the code that uses a DateTime looping variable. The difference presumably is because the AddMonths method takes longer than creating a new DateTime from integer parameters. (If you make a simple test program that compares a loop that uses AddMonths and another that create new integers, you’ll see that this seems to be the case.)


Download Example   Follow me on Twitter   RSS feed   Donate




Posted in performance, syntax | Tagged , , , , , , , , , , , , , , | Leave a comment

Find Friday the Thirteenths in C#

[Friday the Thirteenths]


The following code shows how this example lists the Friday the Thirteenths between selected start and end dates.

// List Friday the 13ths between the start and end dates.
private void btnGo_Click(object sender, EventArgs e)
{
    lstResults.Items.Clear();

    // Get the start and end date components.
    DateTime start_date = dtpStart.Value.Date;
    DateTime end_date = dtpEnd.Value.Date;
    int start_year = start_date.Year;
    int end_year = end_date.Year;

    // Loop over the selected years.
    for (int year = start_year; year <= end_year; year++)
    {
        // Loop over the months in the year.
        for (int month = 1; month <= 12; month++)
        {
            // See if this month's 13th is a Friday.
            DateTime test_date = new DateTime(year, month, 13);

            // If we haven't reached the start date, skip this one.
            if (test_date < start_date) continue;

            // If we've passed the end date, stop looping.
            if (test_date > end_date) break;

            // See if this is a Friday.
            if (test_date.DayOfWeek == DayOfWeek.Friday)
                lstResults.Items.Add(test_date.ToShortDateString());
        }
    }
}

The code first clears the result ListBox. It then gets the start and end dates and uses them to get the start and end year numbers.

Next the program loops between the start and end years, inclusive. For each year, the code loops over the month numbers 1 through 12. For each month, the code creates a DateTime object representing the 13th of the month in the given year.

If the date is before the selected start date, the code uses a continue statement to skip the rest of the month loop and continue with the next month. This could happen up to 11 times (if the start month is December).

If the date is after the end date, the code uses a break statement to break out of the month loop. That happens at most once during the final year, so the year loop also ends at that point. (This won’t happen at all if the end month is December.)

Finally, if the code is still running in the loop, it checks the test date’s DayOfWeek property and adds it to the result ListBox if it is a Friday.

This program is fairly simple (and perhaps not hugely useful for business applications), but it demonstrates two important techniques.

First, when the program gets the start and end dates, it explicitly only takes the date portion of the selected DateTime values. If you compare DateTime variables and you’re not interested in the time part, you still need to be aware of that part. If the code uses a value selected by a DateTimePicker or some other control, you may need to remove the time portion to get the correct results.

In this example, suppose the time selected by dtpStart is Friday June 13, 2014 at 13:30:00. When the code loops through the months, the code considers the date June 13, 2014 but when you initialize the DateTime object, the time is omitted so it defaults to 00:00:00. The test_date > end_date test thinks the test date is before the start date of 13:30:00 so it skips that date.

The program fixes this problem by only using the date portion of the start and end dates. (Note that this problem might lead you to a related issue: thorough testing. In this example, you could find this problem by testing the program with start and end dates that are Friday the Thirteenths.)

The second important technique that this program demonstrates is looping over a range of dates of a month. In the first and last years this code might generate dates that are outside of the desired range and either skip those months or end the month loop without finishing it. Those tests waste a little time. You could avoid them by handling the start year separately, looping over the middle years, and then handling the end year separately, but that would make the code a lot more complicated. This version is much simpler and the tests shouldn’t be a big performance hit. If you’re checking a few thousand or fewer months, you shouldn’t notice any performance penalty.


Download Example   Follow me on Twitter   RSS feed   Donate




Posted in performance, syntax | Tagged , , , , , , , , , , , | 1 Comment

Use == and != to compare structs in C#

[structs]

This example shows how you can overload the == and != operators to make it easier to compare structs.

By default, for reference types (classes), == returns true if two references refer to the same object (reference equality). It returns false if two references refer to different objects that happen to have the same values (value equality).

The article Guidelines for Overloading Equals() and Operator == (C# Programming Guide) says you can override Equals if you like to test value equality, but the article recommends that you leave == alone so it tests reference equality for reference types.

Structs, however, are value types not reference types. A struct’s Equals method is defined by default to return value equality and == isn’t defined at all so you can’t use == to compare two structs.

This example uses the following code to override == for the Person class to check value equality. The Human class is similar but doesn’t override ==.

private struct Person
{
    public string FirstName, LastName;

    public static bool operator ==(Person per1, Person per2)
    {
        return per1.Equals(per2);
    }
    public static bool operator !=(Person per1, Person per2)
    {
        return per1.Equals(per2);
    }

    //public override bool Equals(object obj)
    //{
    //    return base.Equals(obj);
    //}
    //public override int GetHashCode()
    //{
    //    return base.GetHashCode();
    //}
};

private struct Human
{
    public string FirstName, LastName;
};

Both of these classes define FirstName and LastName fields.

The Person class overrides == to make it use the default implementation of Equals to test value equality. It also overrides != to return the negation of ==. Note that == and != come in pairs, so if you override one you must also override the other.

Also note that if you override == and != then Visual Studio warns you if you do not also override Equals and GetHashCode. You can uncomment out the code shown above to make Visual Studio shut up about that.

The following code shows how the program tests == for the Person struct.

private void Form1_Load(object sender, EventArgs e)
{
    Person person1 = new Person() { FirstName = "Rod", LastName = "Stephens" };
    Person person2 = new Person() { FirstName = "Rod", LastName = "Stephens" };
    Person person3 = new Person() { FirstName = "Ann", LastName = "Archer" };
    Human human1 = new Human() { FirstName = "Rod", LastName = "Stephens" };
    Human human2 = new Human() { FirstName = "Rod", LastName = "Stephens" };

    // Use == for Persons.
    string result = "person1 == person2: " +
        (person1 == person2).ToString() + "\r\n";
    result += "person1 == person3: " +
        (person1 == person3).ToString() + "\r\n";

    // Use == for Humans. This isn't allowed.
    //bool humans_equal = (human1 == human2);
    //result += "human1 == human2: " + humans_equal.ToString() + "\r\n";

    txtResults.Text = result;
    txtResults.Select(0, 0);
}

The code makes some Person and Human variables. It then uses == to see whether some of them are equal. The code that compares Human structs is commented out because == is not defined for Human so Visual Studio won’t let that code compile.


Download Example   Follow me on Twitter   RSS feed   Donate




Posted in operators, syntax | Tagged , , , , , , , , , , , , , , , | Leave a comment