Author Archives: Ben

A WPF Numeric Entry Control with Pop-up Calculator

Part 1 – WPF Numeric Entry Control

Part 2 – This article

Download Source Code

When WPF first shipped, there was a noticeable lack of certain controls we’ve become used to in Win32 and WinForms: Calendar, DateTimePicker, and NumericUpDown. WPF 4 adds Calendar and DatePicker, but not anything for numeric entry.

I described how to create a numeric entry control in a previous article. In this article, I will follow that up by adding a popup calculator to allow you to quickly manipulate the number.

If you find this article useful, please consider buying my best-selling book, Writing High-Performance .NET Code.

Adding a Popup Calculator

Now that we have a basic numeric entry control, let’s extend it by adding the power of a keyboard-driven calculator popup window.

If you’ve used Quicken or the now-sadly-defunct Microsoft Money, you’ve seen a feature that pops up a calculator window whenever you hit +, –, *, or / in a number field.

Here are some further requirements for such a control:

  • It should be activated when the user types +, –, *, or /
  • It should be dismissed if the user hits Escape, Enter, or clicks anywhere outside the calculator (which is considered equivalent to Enter, not escape)
  • Both mouse and keyboard input should be handled
  • This version only handles integers
  • It should handle Backspace key to erase previously-input character
  • It should be visually compact
  • It should allow the user to string computations together. A + B – C / D * E

Extend the Numeric Entry Control

We first need to come up with a way to trigger the calculator in our numeric entry control. This is going to simply be if the user hits any math-operation character, such as +, –, *, or /.

First, we’ll need to define the allowed operations and a way to associate keyboard keys with math operations:

public enum MathOperation
{
    None = 0,
    Add,
    Subtract,
    Multiply,
    Divide
};

Now, let’s subclass NumericEntryControl with a new class that adds some additional keyboard handling.

class NumericEntryWithCalcControl : NumericEntryControl
{
    static Dictionary<Key, MathOperation> _keyToOp =
        new Dictionary<Key, MathOperation>();

    static NumericEntryWithCalcControl()
    {
        //associate keyboard keys with a math operation
        _keyToOp[Key.Add] = MathOperation.Add;
        _keyToOp[Key.Subtract] = MathOperation.Subtract;
        _keyToOp[Key.Multiply] = MathOperation.Multiply;
        _keyToOp[Key.Divide] = MathOperation.Divide;

#if DEBUG
        //easier keys on a laptop
        _keyToOp[Key.A] = MathOperation.Add;
        _keyToOp[Key.S] = MathOperation.Subtract;
        _keyToOp[Key.M] = MathOperation.Multiply;
        _keyToOp[Key.D] = MathOperation.Divide;
#endif
    }
}

With this bookkeeping down, we can intercept the keystrokes and show our calculator window (which we’ll define later) in the right spot. We also should handle when the window closes.

private CalcWindow _calc = null;

protected override void OnKeyDown(KeyEventArgs e)
{
    MathOperation op = MathOperation.None;
    if (_keyToOp.TryGetValue(e.Key, out op))
    {
        e.Handled = true;
        HandleOperation(op);
    }
    else
    {
        e.Handled = false;
        base.OnKeyDown(e);
    }
}

private void HandleOperation(MathOperation mathOperation)
{
    //show the calc dialog and initialize with Value and Op
    if (mathOperation != MathOperation.None)
    {
        _calc = new CalcWindow(Value, mathOperation);
        _calc.Closed += new EventHandler(calc_Closed);
        Point point = this.PointToScreen(new Point(0, 0));

        //adjust location to correct for DPI settings
        PresentationSource source = PresentationSource.FromVisual(this);

        double dpiX = 96.0 * source.CompositionTarget.TransformToDevice.M11;
        double dpiY = 96.0 * source.CompositionTarget.TransformToDevice.M22;

        _calc.Left = point.X * 96.0 / dpiX;
        _calc.Top = point.Y * 96.0 / dpiY;

        _calc.Show();
    }
}

private void calc_Closed(object sender, EventArgs e)
{
    if (_calc != null)
    {
        this.Value = _calc.CurrentValue;
        _calc.Closed -= new EventHandler(calc_Closed);
        _calc = null;
    }
}

Notice in ProcessOperation that the location of the calculator window is set to the upper left corner of the NumericEntryControl and it takes into account the current DPI. This is important if you’re using anything other than the standard 96 DPI.

Create the Calculator

calc_demo2

The actual calculator is a WPF window with no border (or a thin one), a textbox, and buttons representing a standard calculator layout.

The screenshot to the right shows the design layout of this window. The C button clears the input and sets the current value to 0. The left arrow erases the last character you input.

Since this window is designed to popup over other controls, I wanted to keep it as compact as possible.

The XAML code looks like this:

<Window x:Class="NumericEntryDemo.CalcWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:my="clr-namespace:NumericEntryDemo"
        Title="CalcWindow" WindowStyle="None"
        SizeToContent="WidthAndHeight"
        WindowStartupLocation="Manual"
        ResizeMode="NoResize"
        BorderThickness="1"
        >
    <DockPanel>
        <TextBox Name="_textbox"
                 DockPanel.Dock="Top"
                 Margin="2"
                 IsReadOnly="True"
                 Text="{Binding RelativeSource={RelativeSource FindAncestor,
                    AncestorType=my:CalcWindow, AncestorLevel=1}, Path=CurrentValue}"
                 TextAlignment="Right" />
        <UniformGrid Columns="4" Rows="5" Width="100" Height="100" Margin="2">
            <Button Name="_button7" Content="7" Click="digitButton_Click"/>
            <Button Name="_button8" Content="8" Click="digitButton_Click"/>
            <Button Name="_button9" Content="9" Click="digitButton_Click"/>
            <Button Name="_buttonDivide" Content="÷"  Click="opButton_Click"/>
            <Button Name="_button4" Content="4" Click="digitButton_Click"/>
            <Button Name="_button5" Content="5" Click="digitButton_Click"/>
            <Button Name="_button6" Content="6" Click="digitButton_Click"/>
            <Button Name="_buttonMultiply" Content="×"  Click="opButton_Click"/>
            <Button Name="_button1" Content="1" Click="digitButton_Click"/>
            <Button Name="_button2" Content="2" Click="digitButton_Click"/>
            <Button Name="_button3" Content="3" Click="digitButton_Click"/>
            <Button Name="_buttonSubtract" Content="-" Click="opButton_Click"/>
            <Button Name="_button0" Content="0" Click="digitButton_Click"/>
            <!-- empty space -->
            <Rectangle/>
            <Rectangle/>
            <Button Name="_buttonAdd" Content="+" Click="opButton_Click"/>
            <Button Name="_buttonClear" Content="C" Click="clearButton_click"/>
            <Button Name="_buttonErase" Content="?" Click="backButton_Click"/>
            <Button Name="_buttonEquals" Content="=" Click="equalsButton_Click"/>
        </UniformGrid>
    </DockPanel>
    <Window.Background>
        <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
            <GradientStop Color="White" Offset="0" />
            <GradientStop Color="#FFDBDBDB" Offset="1" />
        </LinearGradientBrush>
    </Window.Background>
</Window>

All of the click handlers and binding targets will be filled out in the code-behind.

To start with, we need a few housekeeping variables:

  • The original value upon starting the calculator, just in case after all the calculations the user just hits Escape
  • A way to track whether we’ve canceled the whole thing
  • The current operation
  • The current input, in both string and number form
  • The previously calculated value, allowing you to string together calculations like “123 + 456 / 2 * 29”
  • Map buttons and keystrokes to numbers, math operations, and the other commands.

With that, we can see the beginning of our class:

public partial class CalcWindow : Window
{
    private int _initialValue = 0;
    private bool _canceled = false;
    private MathOperation _currentOp = MathOperation.None;

    //value of current input
    public static readonly DependencyProperty CurrentValueProperty =
        DependencyProperty.Register("CurrentValue", typeof(int),
        typeof(CalcWindow), new PropertyMetadata(0));

    //current input in string form
    public static readonly DependencyProperty CurrentInputProperty =
        DependencyProperty.Register("CurrentInput", typeof(string),
        typeof(CalcWindow), new PropertyMetadata(""));

    //previously calculated value
    public static readonly DependencyProperty PreviousValueProperty =
        DependencyProperty.Register("PreviousValue", typeof(int),
        typeof(CalcWindow), new PropertyMetadata(0));

    private static readonly Dictionary<Key, int> _keyDigits =
        new Dictionary<Key, int>();
    private static readonly Dictionary<Key, MathOperation> _keyOps =
        new Dictionary<Key, MathOperation>();

    private readonly Dictionary<Button, int> _buttonDigits =
        new Dictionary<Button, int>();
    private readonly Dictionary<Button, MathOperation> _buttonOps =
        new Dictionary<Button, MathOperation>();

    static CalcWindow()
    {
        _keyDigits[Key.D0] = 0;
        _keyDigits[Key.D1] = 1;
        _keyDigits[Key.D2] = 2;
        _keyDigits[Key.D3] = 3;
        _keyDigits[Key.D4] = 4;
        _keyDigits[Key.D5] = 5;
        _keyDigits[Key.D6] = 6;
        _keyDigits[Key.D7] = 7;
        _keyDigits[Key.D8] = 8;
        _keyDigits[Key.D9] = 9;

        _keyDigits[Key.NumPad0] = 0;
        _keyDigits[Key.NumPad1] = 1;
        _keyDigits[Key.NumPad2] = 2;
        _keyDigits[Key.NumPad3] = 3;
        _keyDigits[Key.NumPad4] = 4;
        _keyDigits[Key.NumPad5] = 5;
        _keyDigits[Key.NumPad6] = 6;
        _keyDigits[Key.NumPad7] = 7;
        _keyDigits[Key.NumPad8] = 8;
        _keyDigits[Key.NumPad9] = 9;

        _keyOps[Key.Add] = MathOperation.Add;
        _keyOps[Key.Subtract] = MathOperation.Subtract;
        _keyOps[Key.Multiply] = MathOperation.Multiply;
        _keyOps[Key.Divide] = MathOperation.Divide;

#if DEBUG
        _keyOps[Key.A] = MathOperation.Add;
        _keyOps[Key.S] = MathOperation.Subtract;
        _keyOps[Key.M] = MathOperation.Multiply;
        _keyOps[Key.D] = MathOperation.Divide;
#endif

    }

    public int CurrentValue
    {
        get
        {
            return (Int32)GetValue(CurrentValueProperty);
        }
        set
        {
            SetValue(CurrentValueProperty, value);
        }
    }

    public string CurrentInput
    {
        get
        {
            return GetValue(CurrentInputProperty) as string;
        }
        set
        {
            SetValue(CurrentInputProperty, value);
        }
    }

    public int PreviousValue
    {
        get
        {
            return (Int32)GetValue(PreviousValueProperty);
        }
        set
        {
            SetValue(PreviousValueProperty, value);
        }
    }

    public CalcWindow(int initialValue, MathOperation operation)
    {
        _initialValue = initialValue;
        _currentOp = operation;

        CurrentValue = _initialValue;
        PreviousValue = CurrentValue;

        InitializeComponent();

        InitializeButtons();
    }

    private void InitializeButtons()
    {
        _buttonDigits[_button0] = 0;
        _buttonDigits[_button1] = 1;
        _buttonDigits[_button2] = 2;
        _buttonDigits[_button3] = 3;
        _buttonDigits[_button4] = 4;
        _buttonDigits[_button5] = 5;
        _buttonDigits[_button6] = 6;
        _buttonDigits[_button7] = 7;
        _buttonDigits[_button8] = 8;
        _buttonDigits[_button9] = 9;

        _buttonOps[_buttonAdd] = MathOperation.Add;
        _buttonOps[_buttonSubtract] = MathOperation.Subtract;
        _buttonOps[_buttonMultiply] = MathOperation.Multiply;
        _buttonOps[_buttonDivide] = MathOperation.Divide;
    }
}

All that’s left is to the actual work of handling input. Since input can be from both keyboard and button clicks, let’s first define methods to handle the general operations, and we can hook up the input later.

Generic Input Handling

First, we’ll look at the generic input handlers. These are called by the mouse and keyboard handlers and they return true so they can notify the caller of whether they actually handled the operation.

The “C” button on the calculator will not only clear the current input, but set the current value to 0 (I’m not going to bother implementing a “CE” button that clears just the current input, but it would be easy to add).

private bool HandleClear()
{
   CurrentInput = "";
   CurrentValue = 0;

   return true;
}

When the user hits escape, you want to close the window, after setting the value back to the initial one.

private bool HandleEscape()
{
    _canceled = true;
    CurrentValue = _initialValue;

    Close();

    return true;
}

We need the _canceled flag because, as we’ll see below, when the window is deactivated, under normal circumstances we want to complete the current operation, but in the canceled case we want to do nothing.

When a user hits Enter or presses the Equals button, the current operation should be completed and the window closed. In this control, since we also want the current operation to be completed when the window is deactivated, hitting Equals will just cause the window to close and we’ll handle the calculation in the deactivation code.

private bool HandleEquals()
{
    //The final operation will be 
    //handled when the window closes
    Close();
    return true;
}

This calculator provides a back button to erase the previously-entered digit. If the input becomes empty, then the current value is set to 0.

private bool HandleBack()
{
    if (!string.IsNullOrEmpty(CurrentInput))
    {
        CurrentInput = CurrentInput.Substring(0, CurrentInput.Length - 1);
        //this is guaranteed to succeed because we validate when we add digits
        if (!string.IsNullOrEmpty(CurrentInput))
        {
            CurrentValue = Int32.Parse(CurrentInput);
        }
        else
        {
            CurrentValue = 0;
        }
    }
    return true;
}

When accepting new input, our button and keyboard handlers will ensure that the user can only enter digits, but we also need to make sure we handle integer overflow. An easy way to do this is to treat the new digit as a character and append it to the current input and then reparse it.

private bool HandleDigit(int digit)
{
    string newInput = CurrentInput + digit.ToString();
    int temp = 0;
    //make sure the full input is a number
    if (Int32.TryParse(newInput, out temp))
    {
        CurrentInput = newInput;
        CurrentValue = temp;
        return true;
    }
    return false;
}

Finally, when the user selects an operation we need to do one of two things. If there is no current input, then we can just change the current operation to the new one. This lets you change your mind, say if you hit ‘+’, but really wanted ‘-‘.

On the other hand, if the user has already entered a number, then you know you’re finishing up the current expression and the user wants to calculate the results and start a new expression. As an example,  consider the expression A + B. A is the initial value of the NumericEntryControl, the + is what triggered the calculator to show up, and B is the number the user entered after the calculator was visible. When the next operation is input, A + B should be resolved, placed into A, the current operation set, and the user can now enter the next B, and so on.

private bool HandleOperation(MathOperation op)
{
    if (CurrentInput.Length > 0)
    {
        ProcessCurrentOperation();
    }
    _currentOp = op;
    return true;
}

With that, we’ve seen all of the generic input handling. Now let’s move on to the actual math.

Doing the Math

The ProcessCurrentOperation method is the heart of this control as it updates all the values according to the user’s input. Here too we need to consider what happens if the user’s calculations cause an integer overflow. To detect this we need to use a combination of the checked keyword and some exception handling. To simplify things, if this happens, the whole calculation is canceled and the window closed. We also handle divide by zero errors by just setting the new value to 0.

(The checked keyword tells .NET to issue exceptions when integer math overflows. The default behavior depends on compilation and environment settings, but the unchecked behavior is usually what happens).

private void ProcessCurrentOperation()
{
    if (string.IsNullOrEmpty(CurrentInput))
    {
        return;
    }

    int newValue = 0;
    checked
    {
        try
        {
            switch (_currentOp)
            {
                case MathOperation.Add:
                    newValue = PreviousValue + CurrentValue;
                    break;
                case MathOperation.Subtract:
                    newValue = PreviousValue - CurrentValue;
                    break;
                case MathOperation.Multiply:
                    newValue = PreviousValue * CurrentValue;
                    break;
                case MathOperation.Divide:
                    if (CurrentValue != 0)
                    {
                        newValue = PreviousValue / CurrentValue;
                    }
                    else
                    {
                        newValue = CurrentValue;
                    }
                    break;
                default:
                    Debug.Assert(false, "Invalid operation. Should never happen!");
                    break;
            }

            CurrentInput = "";
            CurrentValue = newValue;
            PreviousValue = newValue;
        }
        catch (OverflowException)
        {
            //will cause the window to close, so we better cancel everything first
            _canceled = true;
            CurrentValue = _initialValue;

            MessageBox.Show("The result overflowed, calculation canceled.");
        }
    }
}

Deactivation

As we said before, deaction should cause the current operation to complete, unless the user has canceled.

protected override void OnDeactivated(EventArgs e)
{
    if (!_canceled)
    {
        ProcessCurrentOperation();
        if (IsVisible)
        {
            //window is still visible if we've 
            //clicked away from the window
            //before finishing calculation
            Close();
        }
    }
    base.OnDeactivated(e);
}

The only thing that remains is hooking up mouse and keyboard input to our existing methods.

Mouse Handling

These are already referenced in the XAML, and all they do is forward to the  generic handlers.

void opButton_Click(object sender, RoutedEventArgs e)
{
    MathOperation op = _buttonOps[sender as Button];
    e.Handled = HandleOperation(op);
}

void digitButton_Click(object sender, RoutedEventArgs e)
{
    int digit = _buttonDigits[sender as Button];
    e.Handled = HandleDigit(digit);
}

private void clearButton_click(object sender, RoutedEventArgs e)
{
    e.Handled = HandleClear();
}

private void backButton_Click(object sender, RoutedEventArgs e)
{
    e.Handled = HandleBack();
}

private void equalsButton_Click(object sender, RoutedEventArgs e)
{
    e.Handled = HandleEquals();
}

Keyboard Handling

To handle the keyboard, we need to preempt the normal message flow by looking at the preview messages (which flow from the top of the UI hierarchy down, rather than the normal messages which bubble from the bottom to the top). If we don’t do this, then when we hit keys like Enter, they go to the button with the current focus, when instead we want it to always cause the Equals functionality.

protected override void OnPreviewKeyDown(KeyEventArgs e)
{
    /*take over all keyboard input
     * this is important because if a button has focus 
     * and we hit enter then the button will be clicked
     * */

    int digit = 0;
    MathOperation op = MathOperation.None;
    if (_keyDigits.TryGetValue(e.Key, out digit))
    {
        e.Handled = HandleDigit(digit);
    }
    else if (_keyOps.TryGetValue(e.Key, out op))
    {
        e.Handled = HandleOperation(op);
    }
    else
    {
        switch (e.Key)
        {
            case Key.Enter:
                e.Handled = HandleEquals();
                break;
            case Key.Escape:
                e.Handled = HandleEscape();
                break;
            case Key.Back:
                e.Handled = HandleBack();
                break;
            case Key.C:
                e.Handled = HandleClear();
                break;
            default:
                e.Handled = false;
                break;
        }
    }
    base.OnPreviewKeyDown(e);
}

There you have it, a working pop-up calculator, usable from any other control you can subclass. As is often the case, there are a lot more features you could add to this basic implementation:calc_demo3

  • Undo
  • Floating-point support
  • Button highlights with corresponding key presses
  • More advanced display, showing current operator, previous values, etc.
  • More robust handling of overflow

Download Source Code

If you find this article useful, you will love my book, C# 4.0 How-To, which is filled with tips such as this.

New C# 4.0 How-To Review

Of course I  think you should go get my book, but so do other people. 🙂 Here’s a recent, good review of the book: C# 4.0 How-To by Ben Watson.

Some excerpts:

There were a couple things about this book that really compelled me.  The format (this is a first How-To book by Sams for me so I assume it goes across the board) was very much like a focused blog.  It was broken up into small posts about each topic.  The other thing that compelled me was the amount of code samples.

That is definitely the style I was going for and I don’t know if anyone else has picked up on that yet.

Instead, this is a book that you set on your desk and put post-its and dog ears for key sections that you use and use and use before you put the pattern to memory.

I myself use the book in that manner whenever I need a refresh on how to accomplish something.

Go get it from Amazon or B&N or other great bookstores everywhere!

Birth of Emma Watson

Emma-20100820-27On August 6, 2010, our first baby, Emma, was brought into this world. It’s over a month since then, but as you can imagine, I’ve been busy. Smile She’s a beautiful, healthy, wonderful girl. Every day is a new adventure with her, as she learns something new, does something different, changes her patterns, and explores her world. Right now, she’s just fallen asleep with no almost no effort on our part for the first time.

Of course, I had to mark the occasion of her birth with a LEGO crib!

lego_crib-1-Edit

(more at flickr, click the image)

Another C# 4.0 How-To Book Give-away

In celebration of the beginning of the school year here in the USA, I’m going to give away a few more copies of my book C# 4.0 How-To .

If you’ve ever wanted a step-by-step guide with practicable code examples for hundreds of tasks in C#, .Net, and Windows, then this is the book for you. So far, I’m very pleased with the reviews its gotten (if you already have the book and haven’t left a review, why not? Smile)

Just leave a comment on this post, and I’ll choose two people at random. You must be in the US or Canada.

Also, for twitter users, if you retweet a link to this post using the hashtag #cs4howto, I’ll include you in the drawing as well.

The more comments and #cs4howto re-tweets there are, the more books I’ll give away!

Measure Amount of Data to Serialize with a Null Stream

If you’ve got to serialize some data, especially in a binary format, it’s common to output the length of the data. This is useful for versioning, random access, knowing when you’re done reading the records, among other reasons.

Therefore, you need to know the size of the data you’re going to serialize. There are a few ways to do this:

  1. Measure the position you’re at, write the data, measure the new position, subtract, and that’s your length.
  2. If you want to write the length first (which is usually better), you can write a dummy value, such as 0, then writing the data, then backing up in the stream, and writing the real value.
  3. If you can’t back up the stream (very possible in some situations, or undesirable in others), you can measure the amount of data before you write. However, now you have to maintain that code in addition to the actual serialization.
  4. My solution presented here, avoids having to maintain separate code by writing the data to a null stream which does not write any data, but keeps track of how much data was “written.”
class NullStream : System.IO.Stream
{
    public override bool CanRead { get { return false; } }
 
    public override bool CanSeek { get { return false; } }
 
    public override bool CanWrite {get { return true; } }
 
    public override void Flush() { /*do nothing*/ }
 
    public override long Length { get { return Position; } }
 
    private long _position = 0;
    public override long Position 
    { 
        get 
        {
            return _position;
        }
        set
        {
            _position = value;
        }
    }
 
    public override int Read(byte[] buffer, int offset, int count)
    {
        throw new InvalidOperationException();
    }
 
    public override void Write(byte[] buffer, int offset, int count)
    {
        Position += count;
    }
 
    public override long Seek(long offset, System.IO.SeekOrigin origin)
    {
        throw new InvalidOperationException();
    }
 
    public override void SetLength(long value)
    {
        throw new InvalidOperationException();
    }
}

You can use it like this:

long GetDataSize()
{
    using (NullStream stream = new NullStream())
    {
        if (SaveData(stream))
        {
            return stream.Position;
        }
    }
    return 0;
}

There is a downside to something like this: you’re still essentially doing a lot of the work of serialization. Sure, you’re not writing out the bytes anywhere, but if, say, you need to encode a string as bytes before writing to the stream, that’s still going to happen.

Still, this technique made sense in my case, maybe it will work for you.

Get a Free, Autographed Copy of C# 4.0 How-To!

To celebrate how well C# 4.0 How-To is doing, I’m going to give away two free copies of the book!

Here’s how it’s going to work:

1. Leave a comment on this post describing a project you’d like to build with C# 4.

2. I’ll pick two people from those comments at random.

(Make sure you enter your e-mail address where asked—it won’t be published to the blog, but I need it to contact you.)

I’ll leave the comments open for a while and I’ll update this post with the closing date.

Feel free to share a link to this blog post, tweet it, etc. If I get a lot of responses I may give away more.

Thanks to all those have already bought it!

UPDATE 18 May: I am going to close comments on Saturday morning (22 May) and pick the winners then. Thanks for commenting!

UPDATE 22 May: Comments are closed.

Blog moving hosts this weekend

I looked at my site’s settings in Google’s webmaster center and it told me that my site was in the top 5% slowest sites on the Internet. Wow. That really sucks. I’m sorry, dear reader. In any case, this was the final straw, after a string of outages and other general unhappiness. So I’m moving on…

I hope to make this change with no downtime, but who knows who well that will work.