Home > Enterprise >  Automatic field update in WPF XAML binding - simple example not working
Automatic field update in WPF XAML binding - simple example not working

Time:10-26

I'm very new in C#, WPF, XAML and Binding, and to learn this nice environment, I created this little exercise myself: I want to show a price including VAT and excluding VAT. But I want to have the other field be updated automatically as soon as I change a field (so as soon as I hit Tab). I have already created a class (thanks to help in another question I posted in Stack Overflow) to do the math. But my issue us now that the fields don't get updated. I use the INotifyPropertyChange Interface, but for some reason, it just doesn't work. Here's what is happening:

VAT example

I use the following XAML code:

<Window x:Class="BTWv2.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:BTWv2"
        mc:Ignorable="d"
        Title="MainWindow" Height="150" Width="250">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="20" />
            <ColumnDefinition Width="auto" />
            <ColumnDefinition Width="auto" />
            <ColumnDefinition Width="auto" />
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="20" />

        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="20"/>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="20"/>

        </Grid.RowDefinitions>

        <Label Grid.Column="1" Grid.Row="1" Content="Amount inc VAT:" VerticalAlignment="Center" HorizontalAlignment="Center"/>
        <TextBox 
                 Grid.Column="2" Grid.Row="1" 
                 VerticalAlignment="Center" HorizontalAlignment="Center" 
                 Width="100" Text="{Binding IncludeVat,Mode=TwoWay}"/>

        <Label Grid.Column="1" Grid.Row="3" Content="Amount excl VAT:" VerticalAlignment="Center" HorizontalAlignment="Center"/>

        <TextBox Text="{Binding ExcludeVat,Mode=TwoWay}"
               Grid.Column="2" Grid.Row="3" 
               VerticalAlignment="Center" HorizontalAlignment="Center" 
               MinWidth="100"
               />
    </Grid>
</Window>

And here is the actual C# code (I'm using a class named CalculateVAT for the calculation of the numbers:

using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;
namespace BTWv2
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            CalculateVAT Price = new CalculateVAT();
            Price.IncludeVat = 100;
            DataContext = Price;

        }

        public class CalculateVAT : INotifyPropertyChanged
        {
            private decimal m_IncludeVat; // <- decimal is a better choice for finance

            // To compute VAT we should know the percent; let it be known
            public const decimal Percent = 21.0m;


            public decimal IncludeVat
            {
                get => m_IncludeVat;
                set
                {
                    // negative cash are usually invalid; if it's not the case, drop this check
                    if (value < 0)
                        throw new ArgumentOutOfRangeException(nameof(value));
                    m_IncludeVat = value;
                    Tax = Math.Round(m_IncludeVat / 100 * Percent, 2);
                    NotifyPropertyChanged();
                }
            }

            public decimal ExcludeVat
            {
                get => m_IncludeVat - Tax;
                set
                {
                    if (value < 0)
                        throw new ArgumentOutOfRangeException(nameof(value));
                    m_IncludeVat = Math.Round(value / 100 * (100   Percent), 2);
                    Tax = m_IncludeVat - value;
                    NotifyPropertyChanged();
                }
            }

            // Let's be nice and provide Tax value as well as IncludeVat, ExcludeVat 
            public decimal Tax { get; private set; }

            public override string ToString() =>
              $"Include: {IncludeVat:f2}; exclude: {ExcludeVat:f2} (tax: {Tax:f2})";



            public event PropertyChangedEventHandler? PropertyChanged;

            private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
            {
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
            }
        }

    }
}

Has anyone an idea what I'm missing here? I already tried the UpdateSourceTrigger=PropertyChanged setting in the binding, but that doesn't seem to change anything..

CodePudding user response:

Let's say you want to update ExcludeVat when you change IncludeVat from UI. You will need to raise notify property changed for both the properties in setter of IncludeVat.

 public decimal IncludeVat
            {
                get => m_IncludeVat;
                set
                {
                    // negative cash are usually invalid; if it's not the case, drop this check
                    if (value < 0)
                        throw new ArgumentOutOfRangeException(nameof(value));
                    m_IncludeVat = value;
                    Tax = Math.Round(m_IncludeVat / 100 * Percent, 2);
                    NotifyPropertyChanged(nameof(IncludeVat));
                    NotifyPropertyChanged(nameof(ExcludeVat));
                }
            }

What this will do is , when you change IncludeVat from UI , the setter now raises property changed for ExcludeVat. UI will try to update by calling the getter of ExcludeVat. You getter for ExcludeVat : get => m_IncludeVat - Tax; will now get the updated value and UI will reflect that.

  • Related