howamith
howamith

Reputation: 201

Trouble binding a floating point input field in WPF

I recently started playing around with MVVM, and I stumbled across a problem it seems many before me have encountered.

Essentially I had a property of type double in my model, which was linked to a string property in my ViewModel, implementing the INotifyPropertyChanged interface, which was bound to the Text property of a text box whereby the UpdateSourceTrigger is set to PropertyChanged, however the text box would not let me input decimal places or minus signs, nor could I set the text box to blank without the app crashing.

I searched thoroughly for a solution to this and found a lot of possible solutions that worked in some respects but left me with other problems. In the end I've used a combination of things to get around this:

I found this to be of use, and by placing:

public App()
{
    System.Windows.FrameworkCompatibilityPreferences.KeepTextBoxDisplaySynchronizedWithTextProperty = false;
}

in App.xaml.cs I could now insert decimal places in my text box. however I still couldn't use a '-' nor set the text box to blank.

To get around this I did two things. In my XAML I added the following StringFormat to my data binding.

<TextBox Text="{Binding StringLinkedToDouble, UpdateSourceTrigger=PropertyChanged, StringFormat=-N2}"/>

The '-' before the 'N2' allowed me to input a minus sign but unless I typed the numeric value first and then entered the '-' at the beginning of the number the code threw an error. To get around this, and the fact that I couldn't set the text box to blank without running into an error, I did this in my ViewModel:

public StringLinkedToDouble
{
    get { return _model.DoubleToBeLinked.ToString(); }
    set
    {
        if ((value != "") && (value != "-"))
        _model.DoubleToBeLinked = Convert.ToDouble(value);
        RaisePropertyChanged("StringLinkedToDouble");
    }
}

While this may work, I am new to MVVM and the reason I am posting this is because this solution seemed very simple, almost too simple and I'm worried that this may not be 'good' as far as MVVM goes? I'm half expecting to be told that it isn't in fairness ha! And if that is the case then could someone suggest a better alternative? Thanks in advance! Also if there's anything else I've done which isn't proper MVVM then please let me know :)

Upvotes: 0

Views: 4239

Answers (1)

I wouldn't say your solution is "too simple". You've volunteered to do a lot of work the framework wants to do for you.

Here's what I would do:

View model

public class ViewModel : INotifyPropertyChanged
{
    private double _doubleValue = 0;
    public double DoubleValue
    {
        get { return _doubleValue; }
        set
        {
            _doubleValue = value;
            PropertyChanged?.Invoke(this, 
                new PropertyChangedEventArgs(nameof(DoubleValue)));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

XAML

<Window 
    x:Class="WPFTest.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:WPFTest"
    mc:Ignorable="d"
    Title="MainWindow" Height="350" Width="525">

    <Window.DataContext>
        <local:ViewModel />
    </Window.DataContext>

    <Grid>
        <StackPanel Orientation="Vertical">
            <StackPanel Orientation="Horizontal" Margin="2">
                <Label>Double Value</Label>
                <TextBox 
                    Text="{Binding DoubleValue, UpdateSourceTrigger=PropertyChanged}" 
                    Width="300" />
            </StackPanel>
            <StackPanel Orientation="Horizontal" Margin="2">
                <Label>Double Value</Label>
                <TextBox Text="{Binding DoubleValue}" Width="300" />
            </StackPanel>
        </StackPanel>
    </Grid>
</Window>

And a screenshot:

Entering a broken double

The top text box has focus. I typed "-34" and "-34" appeared in the second text box via binding through the viewmodel. Then I typed garbage, and the first TextBox couldn't convert the string to a double, so the default validation showed the red error border -- and the property on the viewmodel wasn't updated, thus the second text box wasn't updated. And the user can see there's a problem.

If your only goal here is just to let the user give you a value for a double, the above is all you need.

If you want to add more UI to help the user understand what's going on, you can write a style or template trigger on Validation.HasError.

I'm not a huge fan of numeric fields refusing to accept non-numeric keystrokes at all, because it's so hard to make it work properly -- as you've seen with your own experience with the minus sign. I'm not religious about it, but I prefer to show them an error and let them decide which characters they want to type and when.

Also, conceptually, your viewmodel should not know about this kind of validation. It makes sense for a viewmodel to worry about a double being out of range, but not about what the user is typing. The viewmodel should have a double property and somebody out there sets it and that's that. Checking which characters the end user typed is a job for the view, not the viewmodel. Leaving it to the TextBox (really, it could be the Binding doing that work; for some reason I never troubled to find out) keeps it in the view where it belongs.

You might try messing with a custom value converter on the binding, but I don't think there's any way to make that do all you're trying to do. But my preference is for using the WPF double conversion the way Microsoft shipped it. I'm generally happy with the way they invented that wheel. One exception: Try to type "-34e10", and it tries to "help" when you get to the '1'. But that's fixable by leaving UpdateSourceTrigger as the default, which for TextBox.Text is LostFocus (that default is determined when the DependencyProperty is registered on the declaring class).

Upvotes: 1

Related Questions