Search code examples
c#data-bindingwindows-runtimeflyout

Databinding not updating in a WinRT Flyout


I have a Flyout (relevant portion below):

<Flyout x:Key="flyoutAverage" FlyoutPresenterStyle="{StaticResource flyoutPresenter}" Closed="Flyout_Closed">
  <Grid>
    <TextBox Grid.Row="1" Grid.Column="0" Text="{Binding One, Mode=TwoWay}" InputScope="Number" Margin="5,0" />
    <TextBox Grid.Row="1" Grid.Column="1" Text="{Binding Two, Mode=TwoWay}" InputScope="Number" Margin="5,0"  />
    <TextBox Grid.Row="1" Grid.Column="2" Text="{Binding Three, Mode=TwoWay}" InputScope="Number" Margin="5,0"  />
    <TextBlock Grid.Row="1" Grid.Column="3" FontSize="18" VerticalAlignment="Center" Text="{Binding Average}" />
 </Grid>
</Flyout>

This is displayed via this button:

<StackPanel Orientation="Horizontal" DataContext="{Binding Neck}">
  ...
  <Button Flyout="{StaticResource flyoutAverage}">Enter</Button>
</StackPanel>

The data context of the button (and the Flyout) is an instance of this object:

public decimal One
{
  get { return m_One; }
  set { m_One = value; PropChanged("First"); PropChanged("Average"); }
}
public decimal Two
{
  get { return m_Two; }
  set { m_Two = value; PropChanged("Second"); PropChanged("Average"); }
}
public decimal Three
{
  get { return m_Three; }
  set { m_Three = value; PropChanged("Third"); PropChanged("Average"); }
}
public decimal Average
{
  get
  {
    return (One + Two + Three) / 3;
  }
}

The intended behavior is that when the user clicks on a button, they are presented with this: Screenshot

The user enters values in each of the text boxes, and the "Average" textblock is automatically updated with the average.

The textboxes are populated with the correct values, but never get pushed back to the object. When I close the Flyout, and open it back up, the values are retained. It's as if the Binding mode was OneWay, when its clearly set as TwoWay.

Any ideas? Or am I just doing it wrong?


Solution

  • You can't directly bind to a decimal from a TextBox without handling a data conversion when you're doing TwoWay binding. The TextBox Text property is of type string. A simple converter like this would be necessary (using IValueConverter (reference)):

    public class DecimalConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, 
                              object parameter, string language)
        {
            return value.ToString();
        }
    
        public object ConvertBack(object value, Type targetType, 
                                  object parameter, string language)
        {
            decimal d;
            if (value is string)
            {
                if (decimal.TryParse((string)value, out d))
                {
                    return d;
                }
            }
            return 0.0;
        }
    }
    

    And as a Resource:

    <local:DecimalConverter x:Key="decimalConverter" />
    

    Then, in use:

    <TextBox Grid.Row="1" Grid.Column="0" 
            Text="{Binding One, Mode=TwoWay, 
                     Converter={StaticResource decimalConverter}}" 
            InputScope="Number" Margin="5,0" />
    

    The InputScope just is a suggestion to Windows as to what keyboard should be displayed by default when the field gets focus. It does not prevent unwanted keys (or alphabetic characters).

    Also, be sure to call PropChanged with the name of the property being changed. They're wrong in your code.

    One thing you should always use when bindings aren't working as expected in a XAML/WPF application is to check the Visual Studio Output window for errors. You would see something like this for example:

    Error: Cannot save value from target back to source. BindingExpression: Path='Two' DataItem='Win8CSharpTest.NeckData, Win8CSharpTest, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'; target element is 'Windows.UI.Xaml.Controls.TextBox' (Name='null'); target property is 'Text' (type 'String').