locked
Textbox binding doesn't update source on change. RRS feed

  • Question

  • I am creating a Widows Store App using C# and XAML.  I am getting my data from a WCF Web Data Service.  I have created a test page that replicates the problem I am having.  I originally get the data from the web service.  When they select the desired item, it serializes it and passes it to the test page.  The test page deserializes it and uses the object for the DataContext.  The data from the source object shows up in the TextBox, but when you change the text, it doesn't push the changes back to the source object.

    <common:LayoutAwarePage
        x:Name="pageRoot"
        x:Class="MPS_Mobile_Estimator.TestPage"
        DataContext="{Binding DefaultViewModel, RelativeSource={RelativeSource Self}}"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="using:MPS_Mobile_Estimator"
        xmlns:common="using:MPS_Mobile_Estimator.Common"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d">
    
        <common:LayoutAwarePage.Resources>
            <common:DateToStringConverter x:Key="dateToStringConverter"/>
    
            <Style x:Key="InputTextBlockStyle" TargetType="TextBlock" BasedOn="{StaticResource BaselineTextStyle}">
                <Setter Property="Margin" Value="5,5,0,0" />
                <!--<Setter Property="FontSize" Value="20" />-->
                <!--<Setter Property="HorizontalAlignment" Value="Right" />-->
            </Style>
    
            <Style x:Key="InputTextBoxStyle" TargetType="TextBox">
                <Setter Property="MinHeight" Value="{StaticResource TextControlThemeMinHeight}" />
                <Setter Property="MinWidth" Value="{StaticResource TextControlThemeMinWidth}" />
                <Setter Property="Margin" Value="5" />
            </Style>
    
            <Style x:Key="InputBorderStyle" TargetType="Border">
                <Setter Property="BorderThickness" Value="1" />
                <Setter Property="BorderBrush" Value="{StaticResource ButtonBorderThemeBrush}" />
                <Setter Property="Padding" Value="5" />
                <Setter Property="Margin" Value="0,5" />
            </Style>
    
        </common:LayoutAwarePage.Resources>
        <!--
            This grid acts as a root panel for the page that defines two rows:
            * Row 0 contains the back button and page title
            * Row 1 contains the rest of the page layout
        -->
        <Grid Style="{StaticResource LayoutRootStyle}">
            <Grid.RowDefinitions>
                <RowDefinition Height="140"/>
                <RowDefinition Height="*"/>
            </Grid.RowDefinitions>
    
            <!-- Back button and page title -->
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="Auto"/>
                    <ColumnDefinition Width="*"/>
                </Grid.ColumnDefinitions>
                <Button x:Name="backButton" Click="GoBack" IsEnabled="{Binding Frame.CanGoBack, ElementName=pageRoot}" Style="{StaticResource BackButtonStyle}"/>
                <TextBlock x:Name="pageTitle" Grid.Column="1" Text="{StaticResource AppName}" Style="{StaticResource PageHeaderTextStyle}"/>
    
            </Grid>
                <Grid Name="InputSection" Grid.Row="1">
                    <Grid.RowDefinitions >
                        <RowDefinition />
                    </Grid.RowDefinitions>
                    <Border Grid.Row="0" Style="{StaticResource InputBorderStyle}">
                        <Grid>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="33*" />
                                <ColumnDefinition Width="33*" />
                                <ColumnDefinition Width="33*" />
                            </Grid.ColumnDefinitions>
                            <Grid.RowDefinitions >
                                <RowDefinition Height="Auto" />
                                <RowDefinition Height="Auto" />
                                <RowDefinition Height="*" />
                            </Grid.RowDefinitions>
                            <TextBlock Text="Shipment" Style="{StaticResource SubheaderTextStyle}" />
                            <StackPanel Grid.Row="1" >
                                <TextBlock Text="Est. Weight" Style="{StaticResource InputTextBlockStyle}" />
                                <TextBox Name ="txtWeight" Text="{Binding Weight}" Grid.Column="1" Style="{StaticResource InputTextBoxStyle}" />
                            </StackPanel>
                        </Grid>
                    </Border>
                </Grid>
    
        </Grid>
    </common:LayoutAwarePage>
    

    The code behind is as follows:

    using MPS_Mobile_Estimator.Common;
    using MPS_Mobile_Estimator.DataModel;
    using MPS_Mobile_Estimator.DispatchServiceReference;
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.IO;
    using System.Linq;
    using Windows.Foundation;
    using Windows.Foundation.Collections;
    using Windows.UI.Xaml;
    using Windows.UI.Xaml.Controls;
    using Windows.UI.Xaml.Controls.Primitives;
    using Windows.UI.Xaml.Data;
    using Windows.UI.Xaml.Input;
    using Windows.UI.Xaml.Media;
    using Windows.UI.Xaml.Navigation;
    
    // The Basic Page item template is documented at http://go.microsoft.com/fwlink/?LinkId=234237
    
    namespace MPS_Mobile_Estimator
    {
        /// <summary>
        /// A basic page that provides characteristics common to most applications.
        /// </summary>
        public sealed partial class TestPage : MPS_Mobile_Estimator.Common.LayoutAwarePage
        {
            public TestPage()
            {
                this.InitializeComponent();
            }
    
            protected override void LoadState(Object navigationParameter, Dictionary<String, Object> pageState)
            {
                // Allow saved page state to override the initial item to display
                if (pageState != null && pageState.ContainsKey("SelectedEstimate"))
                {
                    navigationParameter = pageState["SelectedEstimate"];
                }
    
                //var Est = await DispatchDataSource.GetEstimate(navigationParameter.ToString());
                var Est = StringSerializer.DeserializeObject<Estimate>((string)navigationParameter);
                InputSection.DataContext = Est;
            }
    
            protected override void SaveState(Dictionary<String, Object> pageState)
            {
                var Est = (Estimate)InputSection.DataContext;
                var tmp = txtWeight.Text; //Shows the updated value
                var tmp1 = Est.Weight; //Shows the original value
                pageState["SelectedEstimate"] = StringSerializer.SerializeObject(Est);
            }
    
        }
    }
    

    I was running under the assumption that setting the DataContext for an object automatically sets the DataContext for its children.  This seems to work as far as the data from the source object does show up in the TextBox.  I am apparently missing something that pushes the data back to the source when it changes.

    Any help with this would be greatly appreciated.

    Jim

    Friday, October 18, 2013 2:21 PM

Answers

  • Thanks for your reply Sachin.  You are correct that the property would not be updated until the focus changed.  This was actually OK.  I found that there were two things wrong with what I was doing.  The first was the binding.  I need to do it as follows:

    <TextBox Name ="txtWeight" Text="{Binding Weight, Mode=TwoWay, Converter={StaticResource intToStringConverter}}" Grid.Column="1" Style="{StaticResource InputTextBoxStyle}" />
    

    The Key things here were the "Mode=TwoWay" and the Converter. Apparently the default mode is OneWay. I found the converter is needed because the Type in the database is Int. I have worked with VB for years and it would generally take care of type conversions. The Converter I used is pretty straight forward:

        public class IntToStringConverter : IValueConverter
        {
    
            #region IValueConverter Members
    
            // Define the Convert method to change a IntTime object to 
            // a string.
            public object Convert(object value, Type targetType,
                object parameter, string language)
            {
                if ((value != null) && (value.GetType() == typeof(int)))
                {
                    int tempInt = (int)value;
                    return tempInt.ToString();
                }
                return null;
            }
    
            // ConvertBack is not implemented for a OneWay binding.
            public object ConvertBack(object value, Type targetType,
                object parameter, string language)
            {
                int tmp;
                int.TryParse(value.ToString(), out tmp);
                return tmp;
            }
    
            #endregion
        }
    

    If you are looking at this for reference, don't forget to add the reference to the converter in your page resources:

    <common:LayoutAwarePage.Resources> <common:IntToStringConverter x:Key="intToStringConverter"/> </common:LayoutAwarePage.Resources>

    Monday, October 21, 2013 2:06 PM

All replies

  • By default update of target property will happen on textbox focus change instead of text change. Due to lack of UpdateSourceTrigger on WinRT Binding we cannot change this behavior. You probably need to listen to text change event in code behind and update the data model (deviation from MVVM). 

    See this thread for more details. 


    Thanks, Sachin



    • Edited by Sachin S Friday, October 18, 2013 2:29 PM
    Friday, October 18, 2013 2:28 PM
  • Thanks for your reply Sachin.  You are correct that the property would not be updated until the focus changed.  This was actually OK.  I found that there were two things wrong with what I was doing.  The first was the binding.  I need to do it as follows:

    <TextBox Name ="txtWeight" Text="{Binding Weight, Mode=TwoWay, Converter={StaticResource intToStringConverter}}" Grid.Column="1" Style="{StaticResource InputTextBoxStyle}" />
    

    The Key things here were the "Mode=TwoWay" and the Converter. Apparently the default mode is OneWay. I found the converter is needed because the Type in the database is Int. I have worked with VB for years and it would generally take care of type conversions. The Converter I used is pretty straight forward:

        public class IntToStringConverter : IValueConverter
        {
    
            #region IValueConverter Members
    
            // Define the Convert method to change a IntTime object to 
            // a string.
            public object Convert(object value, Type targetType,
                object parameter, string language)
            {
                if ((value != null) && (value.GetType() == typeof(int)))
                {
                    int tempInt = (int)value;
                    return tempInt.ToString();
                }
                return null;
            }
    
            // ConvertBack is not implemented for a OneWay binding.
            public object ConvertBack(object value, Type targetType,
                object parameter, string language)
            {
                int tmp;
                int.TryParse(value.ToString(), out tmp);
                return tmp;
            }
    
            #endregion
        }
    

    If you are looking at this for reference, don't forget to add the reference to the converter in your page resources:

    <common:LayoutAwarePage.Resources> <common:IntToStringConverter x:Key="intToStringConverter"/> </common:LayoutAwarePage.Resources>

    Monday, October 21, 2013 2:06 PM