Sabyasachi Mukherjee
Sabyasachi Mukherjee

Reputation: 466

Binding textbox to Decimal "fallback value" on input error

I was testing the nuances of Data Binding in WPF today, and I came across a strange problem.

In my View, I have

<TextBox Text="{Binding MyInt, UpdateSourceTrigger=LostFocus, StringFormat='{}{##.##}'}"/>        
<Label Content="{Binding MyStr2}"/>

In my ViewModel, I have

    private decimal myInt;

    public decimal MyInt
    {
        get { return myInt; }
        set
        {
            if (value == myInt) { return; }
            myInt = value;               
            OnPropertyChange();
            OnPropertyChange("MyStr2");
        }
    }

    public string MyStr2
    {
        get
        {
            return myInt.ToString("N2", CultureInfo.CreateSpecificCulture("en-IN"));
        }

    }

I want to get the data from the textbox, and format it properly and display it in a label. Simple and easy.

Now, I can only enter decimal data in the textbox, or else the border turns red, indicating an input error.

All is fine when I input decimal data:Working fine

But, when I enter garbage data, this happens:Error

The red border shows there is data input error. But, the label still reflects the older data. I want the label to fall back to 0.00 in case of input error. Is there any way to do that?

I have tried to find a contrived way to do it, and it works, sort of, in an extremely hacky kludgy way.

    private string myInt;
    private decimal myIntActual;

    public string MyInt
    {
        get
        {
            return myInt;
        }
        set
        {
            if (value == myInt) { return; }
            Decimal.TryParse(value.ToString(), out myIntActual);
            myInt = myIntActual.ToString();
            Decimal.TryParse(myInt, out myIntActual);
            OnPropertyChange();
            OnPropertyChange("MyStr2");
        }
    }

    public string MyStr2
    {
        get
        {
            return myIntActual.ToString("N2", CultureInfo.CreateSpecificCulture("en-IN"));
        }
    }

What this does is that, it takes the input as a string, tries to parse it to decimal, if it cant, it will return zero. But I have sacrificed the input validation with this code, not to mention that the code looks ugly.

I understand that WPF has a fallback value mechanism when binding fails. Is there any fallback value mechanism in case of input error?

EDIT:

One more thing. After entering the garbage data, the next time I enter valid data, the value somehow reaches the viewmodel, but the textbox becomes blank. And it is a reproducible problem.

Screenshot A:

Working correctly

This is working as is expected.

Garbage data

The garbage data is then entered. Do note that, as the UpdateSourceTrigger is LostFocus, the error does not come up until the focus is lost.

Valid data

I then enter valid data, and...

Gone!

on losing focus, the textbox blanks out. Do note, that the label is properly updated. I set up breakpoints to see if the binded property is updated or not, and it is, yet the textbox becomes blank. Why is that?

Upvotes: 1

Views: 735

Answers (1)

Here's the trigger idea. I tested this and it's working for me. It works with no changes to your viewmodel or code behind.

<TextBox 
    x:Name="MyIntTextBox"
    Text="{Binding MyInt, UpdateSourceTrigger=LostFocus, StringFormat='{}{##.##}'}"
    />
<Label Content="{Binding MyStr2}">
    <Label.Style>
        <Style TargetType="Label" BasedOn="{StaticResource {x:Type Label}}">
            <Style.Triggers>
                <!-- 
                parens on (Validation.HasError) are important: It's an attached 
                so its name has a dot in it. The parens tell the binding that.
                --> 
                <DataTrigger 
                    Binding="{Binding (Validation.HasError), ElementName=MyIntTextBox}" 
                    Value="True">
                    <Setter 
                        Property="Visibility" 
                        Value="Hidden" 
                        />
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </Label.Style>
</Label>

As for the blank-out behavior, I see that when I type an invalid string into the MyInt box, tab out, tab back in, type a valid value, and tab back out. It only happens when I tab out on a valid value, when the previous value that I tabbed out on was invalid. I think that's exactly the (mis)behavior you're describing.

I don't have an explanation for that. I don't like to run around yelling "bug" on a framework, but I think that's a bug. The control isn't showing the viewmodel property value it's bound to, and the value hasn't changed. I put a breakpoint in the MyInt setter and it only hits the breakpoint once, with the new valid value (aside from the fact that as far as I know there's no integer value that's displayed as an empty string).

I would ask about that as a separate question if google doesn't turn up any workarounds. I tried "wpf textbox invalid tab out empty" and got nothing.

Upvotes: 1

Related Questions