question

RogerSchlueter-7899 avatar image
0 Votes"
RogerSchlueter-7899 asked RogerSchlueter-7899 commented

How to do arithmetic with textboxes

I have two textboxes on a wpf window that contain numeric values; call them txt1 and txt2. I want a third textbox (txt3) to show the difference between those two values.

I created a Property in code-behind called Diff defined as:

 Diff = Cdec(txt1.Text) - Cdec(txt2.Text)

and bound that value as follows:

 txt3.Text={Binding Path=Diff

However, at run-time the Property Diff is not found. How do I make that value available to be bound?


windows-wpf
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

PeterFleischer-3316 avatar image
0 Votes"
PeterFleischer-3316 answered RogerSchlueter-7899 commented

Hi Roger, don't forget:

  • set DataContext or Source

  • Notify property changed

  • check input for invalid decimal numbers

Try following simple demo.

XAML:

 <Window x:Class="Window014"
         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:WpfApp1"
         mc:Ignorable="d"
         Title="Window014" Height="450" Width="800" Loaded="Window_Loaded">
     <StackPanel>
     <TextBox x:Name="txt1" Margin="5" />
     <TextBox x:Name="txt2" Margin="5"/>
     <TextBox Text="{Binding Diff}" Margin="5"/>
   </StackPanel>
 </Window>

CodeBehind:

 Imports System.ComponentModel
 Imports System.Runtime.CompilerServices
    
 Public Class Window014
   Implements INotifyPropertyChanged
    
   Private Sub Window_Loaded(sender As Object, e As RoutedEventArgs)
     Me.DataContext = Me
   End Sub
    
   Private Sub txt_TextChanged(sender As Object, e As TextChangedEventArgs) Handles txt1.TextChanged, txt2.TextChanged
     Dim dec1 As Decimal = 0
     Decimal.TryParse(txt1.Text, dec1)
     Dim dec2 As Decimal = 0
     Decimal.TryParse(txt2.Text, dec2)
     Diff = dec1 - dec2
     OnPropChanged(NameOf(Diff))
   End Sub
    
   Public Property Diff As Decimal
    
   Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
   Private Sub OnPropChanged(<CallerMemberName> Optional propName As String = "")
     RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propName))
   End Sub
    
 End Class


· 11
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

I like this approach, Peter, it's clean and simple. Except, as I explained to Alex, I cannot changed the DataContext of the window because so many other controls on the window use a different DataContext.

I tried several ways to modify your approach to see if I could work around the DataContext issue but finally gave up. Now I just compute the value and assign it to the tb3.Text property. Nevertheless, thanks for the help.

0 Votes 0 ·

Hi Roger, you can set the DataContext only for whole TextBox:

     <TextBox x:Name="txt1" Margin="5" />
     <TextBox x:Name="txt2" Margin="5"/>
     <TextBox x:Name="txt3" Text="{Binding Diff}" Margin="5"/>
    
   Private Sub Window_Loaded(sender As Object, e As RoutedEventArgs)
     txt3.DataContext = Me
   End Sub
0 Votes 0 ·

I used your approach to set the DataContext of txt3. I defined Diff as:

 Public Property Diff as Decimal

and set the binding of tb3 as:

 Text="{Binding Path=Diff, Mode=OneWay}"

and implemented INotifyPropertyChanged on the relevant class but I cannot figure out how to implement the change notification. Using these:

 OnPropertyChanged(Diff)
 OnPropertyChanged("Diff")
 OnPropertyChanged(NameOf(Diff"))

all give compile time errors.

If I am on the right track(?) then how do I implement change notification.

0 Votes 0 ·
Show more comments

Hi Roger, if you cannot use the DataContext you can use Source und assign binding by code:

     <TextBox x:Name="txt1" Margin="5" />
     <TextBox x:Name="txt2" Margin="5"/>
     <TextBox x:Name="txt3" Margin="5"/>
    
    
   Private Sub Window_Loaded(sender As Object, e As RoutedEventArgs)
     txt3.SetBinding(TextBox.TextProperty, New Binding("Diff") With {.Source = Me})
   End Sub


0 Votes 0 ·

Hi Roger, if your property for binding is located in CodeBehind of Window you can work without DataContext and use only RelativeSoure like this:

 <TextBox x:Name="txt1" Margin="5" />
 <TextBox x:Name="txt2" Margin="5"/>
 <TextBox Text="{Binding Diff, RelativeSource={RelativeSource AncestorType=Window}}" Margin="5"/>


0 Votes 0 ·

Hi Roger, an another approach without using DataContext is to use MultiValueConverter (see Alex):

 <TextBox Margin="5" IsReadOnly="True">
   <TextBox.Resources>
     <local:Converter x:Key="conv"/>
   </TextBox.Resources>
   <TextBox.Text>
     <MultiBinding Converter="{StaticResource conv}">
       <Binding Path="Text" ElementName="txt1"/>
       <Binding Path="Text" ElementName="txt2"/>
     </MultiBinding>
   </TextBox.Text>
 </TextBox>
    
 Public Function Convert(values() As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IMultiValueConverter.Convert
   Dim dec1 As Decimal = 0
   Decimal.TryParse(values(0).ToString, dec1)
   Dim dec2 As Decimal = 0
   Decimal.TryParse(values(1).ToString, dec2)
   Return (dec1 - dec2).ToString
0 Votes 0 ·
AlexLi-MSFT avatar image
0 Votes"
AlexLi-MSFT answered RogerSchlueter-7899 commented

Welcome to our Microsoft Q&A platform!

I think you can use MultiBinding:

 Imports System.ComponentModel
 Imports System.Globalization
 Imports System.Runtime.CompilerServices
    
 Class MainWindow
     Implements INotifyPropertyChanged
     Public Sub New()
    
         ' This call is required by the designer.
         InitializeComponent()
    
         ' Add any initialization after the InitializeComponent() call.
         Me.DataContext = Me
    
    
     End Sub
    
     Private _tb1 As String
     Public Property tb1() As String
         Get
             Return _tb1
         End Get
         Set(value As String)
             _tb1 = value
             NotifyPropertyChanged("tb1")
         End Set
     End Property
    
    
     Private _tb2 As String
     Public Property tb2() As String
         Get
             Return _tb2
         End Get
         Set(value As String)
             _tb2 = value
             NotifyPropertyChanged("tb2")
         End Set
     End Property
    
     Private _Diff As String
    
     Public Property Diff() As String
         Get
             Return _Diff
    
         End Get
         Set(value As String)
             _Diff = value
             NotifyPropertyChanged("Diff")
         End Set
     End Property
     Public Event PropertyChanged As PropertyChangedEventHandler _
           Implements INotifyPropertyChanged.PropertyChanged
    
     Private Sub NotifyPropertyChanged(ByVal info As String)
         RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(info))
     End Sub
    
    
    
    
 End Class
 Public Class MultiStringStateConverter
     Implements IMultiValueConverter
    
    
     Private Function IMultiValueConverter_Convert(value() As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IMultiValueConverter.Convert
    
         Return (System.Convert.ToInt32(value(0)) - System.Convert.ToInt32(value(1))).ToString()
    
     End Function
    
     Private Function IMultiValueConverter_ConvertBack(value As Object, targetTypes() As Type, parameter As Object, culture As CultureInfo) As Object() Implements IMultiValueConverter.ConvertBack
         Throw New NotImplementedException()
     End Function
 End Class


xaml:

   <Window.Resources>
         <local:MultiStringStateConverter x:Key="MultiStringStateConverter"/>
     </Window.Resources>
     <StackPanel>
         <TextBox Name="txt1" Text="{Binding tb1,UpdateSourceTrigger=PropertyChanged}"/>
         <TextBox Name="txt2" Text="{Binding tb2,UpdateSourceTrigger=PropertyChanged}"/>
         <TextBox Name="txt3">
             <TextBox.Text>
                 <MultiBinding UpdateSourceTrigger="PropertyChanged" Converter="{StaticResource MultiStringStateConverter}">
                     <Binding Path="tb1"/>
                     <Binding Path="tb2"/>
                 </MultiBinding>
             </TextBox.Text>
         </TextBox>
     </StackPanel>


7846-1.gif



1.gif (34.2 KiB)
· 1
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

That's a good solution but I could not use it directly because I cannot set the DataContext of the window to itsself because many other controls rely on a different window context.

I tried numerous approaches using your idea but finally decided to skip the binding approach and just compute the value and assign it to the tb3.Text property. Nevertheless, thanks for the help.

0 Votes 0 ·
AlexLi-MSFT avatar image
0 Votes"
AlexLi-MSFT answered

If you don't want set the DataContent to itself,you can use the following code:

 Imports System.ComponentModel
 Imports System.Globalization
 Imports System.Runtime.CompilerServices
    
 Class MainWindow
     Public Sub New()
         ' This call is required by the designer.
         InitializeComponent()
         ' Add any initialization after the InitializeComponent() call.
         Me.DataContext = New MyViewModel()
     End Sub
 End Class
 Public Class MyViewModel
     Implements INotifyPropertyChanged
    
     Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
     Private Sub NotifyPropertyChanged(ByVal info As String)
         RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(info))
     End Sub
     Private _tb1 As String = 0
     Public Property tb1() As String
         Get
             Return _tb1
         End Get
         Set(value As String)
             _tb1 = value
             NotifyPropertyChanged("tb1")
         End Set
     End Property
    
    
     Private _tb2 As String = 0
     Public Property tb2() As String
         Get
             Return _tb2
         End Get
         Set(value As String)
             _tb2 = value
             NotifyPropertyChanged("tb2")
         End Set
     End Property
    
     Private _Diff As String
    
     Public Property Diff() As String
         Get
             Return _Diff
    
         End Get
         Set(value As String)
             _Diff = value
             NotifyPropertyChanged("Diff")
         End Set
     End Property
 End Class
 Public Class MultiStringStateConverter
     Implements IMultiValueConverter
    
    
     Private Function IMultiValueConverter_Convert(value() As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IMultiValueConverter.Convert
    
         Return (System.Convert.ToInt32(value(0)) - System.Convert.ToInt32(value(1))).ToString()
    
     End Function
    
     Private Function IMultiValueConverter_ConvertBack(value As Object, targetTypes() As Type, parameter As Object, culture As CultureInfo) As Object() Implements IMultiValueConverter.ConvertBack
         Throw New NotImplementedException()
     End Function
 End Class
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.