Search code examples
c#.netxamlwindows-phone

Windows Phone Data-bound TextBox Automatically Updates Souce To a Rounded Value


I have a Windows Phone page that displays information about a geographical point including latitude, longitude and distance from the previous point.

<StackPanel DataContext="{Binding Points}">
    <TextBox Text="{Binding Lat, Mode=TwoWay}" />
    <TextBox Text="{Binding Lon, Mode=TwoWay}" />
    <TextBox Text="{Binding Distance, Mode=TwoWay}" />
</StackPanel>    

It is bound to the view model that also recalculates coordinates and issues PropertyChanged event for all members.

    public double Distance
    {
        get { return _distance; }
        set 
        {
            _distance = value;
            _lat = 12.34567890123456;
            _lon = 34.567890123456;
            NotifyPropertyChanged(String.Empty);
        }
    }
    private double _distance;

    public double Lon
    {
        get { return _lon; }
        set { 
            _lon = value;
            NotifyPropertyChanged(String.Empty); 
        }
    }
    private double _lon;

    public double Lat
    {
        get { return _lat; }
        set 
        { 
            _lat = value;
            NotifyPropertyChanged(String.Empty); 
        }
    }
    private double _lat;

I also have a simple function that updates this property for test purposes.

    private void AppButton_Click(object sender, RoutedEventArgs e)
    {
        GeoPoint currentPoint = ViewModel.Points.View.CurrentItem as GeoPoint;
        currentPoint.Distance= 1000;
    }

So what happens is when I click the button, first the "set" for Distance is called, but nanoseconds after the "set" for Lat and Lon are also called from an external code and with rounded value (values I can actually see in the controls). Specifically in a "set" for Lon value that comes as a parameter is 34.5679. This makes my application to update the database with rounded (displayed) values instead of actual values.

If I set the bidding mode to OneWay for Lat and Lon controls, the behavior is as expected. According to MSDN documentation, TwoWay binding to a TextBox updates the source by default when the focus is lost. In my example Lat and Lon never got focus.

What is the reason of this behavior? Is there a way to configure TextBox not to update the bound value when it's not actually changed by the user?

UPDATE: I can reproduce this behavior even easier by calling

currentPoint.Distance = 123.456789123;

In this case the "set" for Distance is called twice - first with value==123.456789123, and 2nd time with value==123.457.

UPDATE2: Here is calls stack for the first call:

>   GeoPoints.exe!GeoPoints.DataModel.GeoPoint.Distance.set(double value) Line 144  C#
GeoPoints.exe!GeoPoints.PointPage.AppButtonCancel_Click(object sender, Windows.UI.Xaml.RoutedEventArgs e) Line 121  C#

And for the second call:

>   GeoPoints.exe!GeoPoints.DataModel.GeoPoint.Distance.set(double value) Line 144  C#
[Native to Managed Transition]  
mscorlib.ni.dll!System.Runtime.InteropServices.WindowsRuntime.CustomPropertyImpl.InvokeInternal(object target, object[] args, bool getValue)    Unknown
mscorlib.ni.dll!System.Runtime.InteropServices.WindowsRuntime.CustomPropertyImpl.SetValue(object target, object value)  Unknown

Solution

  • I was able to fully fix the issue using the following converter:

    using System;
    using Windows.UI.Xaml.Data;
    
    public class DoubleConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, string language)
        {
            var doubleNumber = (double)value;
            return doubleNumber.ToString("0.00#################");
        }
    
        public object ConvertBack(object value, Type targetType, object parameter, string language)
        {
            var stringValue = (string)value;
            return double.Parse(stringValue);
        }
    }
    

    You can use it in your XAML like this:

    <Page
    x:Class="PhoneAnswer001.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:PhoneAnswer001"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    DataContext="{Binding Source={StaticResource Locator}, Path=Main}"
    Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <Page.Resources>
        <local:DoubleConverter x:Key="DoubleConverter"/>
    </Page.Resources>
    
    <Grid>
        <StackPanel DataContext="{Binding Points}">
            <TextBox Text="{Binding Latitude, Converter={StaticResource DoubleConverter}, Mode=TwoWay}" InputScope="NumberFullWidth"/>
            <TextBox Text="{Binding Longitude, Converter={StaticResource DoubleConverter}, Mode=TwoWay}" InputScope="NumberFullWidth" />
            <TextBox Text="{Binding Distance, Converter={StaticResource DoubleConverter}, Mode=TwoWay}" InputScope="NumberFullWidth" />
        </StackPanel>
        <Button x:Name="CancelButton" Command="{Binding Path=CancelCommand}"/>
    </Grid>
    </Page>
    

    You can edit the format in the converter to fit your needs or you can even add it as a parameter for the converter.

    I hope this is more maintainable.