سؤال

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?

هل كانت مفيدة؟

المحلول

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').

نصائح أخرى

Just to share if anyone faces this problem in the future, I used the above converter but was racking my brain as to why the conversion wouldn't work. As it happens, the Decimal separator for my locale was "," instead of a ".". So, in order to deal with this I've modified the converter as follows:


    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)
        {
            if (value is string)
            {
                decimal d;
                var formatinfo = new NumberFormatInfo();

                formatinfo.NumberDecimalSeparator = ".";

                if (decimal.TryParse((string)value, NumberStyles.Float, formatinfo, out d))
                {
                    return d;
                }

                formatinfo.NumberDecimalSeparator = ",";

                if (decimal.TryParse((string)value, NumberStyles.Float, formatinfo, out d))
                {
                    return d;
                }
            }
            return 0.0;
        }
    }

I prefer a more general solution dealing with string formatting and binding:

 public class StringFormatConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, string language)
    {
        if (value == null)
        {
            return string.Empty;
        }
        return string.Format((parameter as string) ?? "{0}", value);
    }

    public object ConvertBack(object value, Type targetType, object parameter, string language)
    {
        if (string.IsNullOrEmpty(System.Convert.ToString(value)))
        {
            if (targetType.IsNullable())
            {
                return null;
            }
            return 0;
        }

        if (targetType == typeof (double))
        {
            return System.Convert.ToDouble(value);
        }
        if (targetType == typeof(decimal))
        {
            return System.Convert.ToDecimal(value);
        }
        if (targetType == typeof(int))
        {
            return System.Convert.ToInt16(value);
        }
        if (targetType == typeof(Int32))
        {
            return System.Convert.ToInt32(value);
        }
        return value;
    }
}

Usage:

<TextBox InputScope="Number" Text="{Binding Path=Model.SalesPrice, Mode=TwoWay, Converter={StaticResource StringFormatConverter}, ConverterParameter='{}{0:N2}'}">

This converts both the display value and the entered value back to the source.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top