I am trying to understand how ValueConverters work. I have three Text Boxes being txtQty, txtPrice and txtAmount representing Qty, Price and Amount respectively and Amount = Qty x Price.
txtQty and txtPrice are unbound controls whilst txtAmount is bound to a DataTable in a DataSet.
How can I update the value in txtAmount which is bound to a DataTable using ValueConveter which takes txtQty and txtPrice as input values?
I can easily achieve this in many ways. But I want to use a ValueConverter for this.
Any ideas?
You can create a converter that implements IMultiValueConverter to compute for your Price and Qty.
public class AmountConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
decimal qty = 0;
decimal price = 0;
if (values?.Length < 2)
throw new ArgumentNullException("Parameter should contain 2 values");
if (!string.IsNullOrEmpty(values[0].ToString()) && !decimal.TryParse(values[0].ToString(), out qty))
throw new ArgumentException("1st value should be decimal.");
if (!string.IsNullOrEmpty(values[1].ToString()) && !decimal.TryParse(values[1].ToString(), out price))
throw new ArgumentException("2nd value should be decimal.");
return (qty * price).ToString();
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Then use MultiBinding for your Amount textbox
<TextBox x:Name="txtAmount" HorizontalAlignment="Left" IsReadOnly="True">
<TextBox.Text>
<MultiBinding Converter="{StaticResource AmountConverter}">
<Binding ElementName="txtQty" Path="Text" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged"/>
<Binding ElementName="txtPrice" Path="Text" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged"/>
</MultiBinding>
</TextBox.Text>
</TextBox>
However you may have to do some interactivity with your txtQty and txtPrice to update your viewmodel-bound Amount, you may also need to invoke a command from your vm to accomplish this.
Listing the entire test xaml and viewmodel code...
<Window x:Class="WpfApp2.MainWindow"
x:Name="root"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:interactivity="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp2"
xmlns:vm="clr-namespace:WpfApp2.ViewModel"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<vm:ViewModelTest />
</Window.DataContext>
<Window.Resources>
<local:AmountConverter x:Key="AmountConverter" />
</Window.Resources>
<Grid Margin="12 0 0 0" >
<StackPanel>
<StackPanel Orientation="Vertical">
<TextBlock HorizontalAlignment="Left" Text="Qty" Margin="0 0 12 0" />
<TextBox x:Name="txtQty" HorizontalAlignment="Left" Height="20" Width="50" >
<interactivity:Interaction.Triggers>
<i:EventTrigger EventName="TextChanged">
<i:InvokeCommandAction Command="{Binding DataContext.UpdateAmountCommand, ElementName=root}" CommandParameter="{Binding Path=Text, ElementName=txtAmount}" />
</i:EventTrigger>
</interactivity:Interaction.Triggers>
</TextBox>
</StackPanel>
<StackPanel Orientation="Vertical" >
<TextBlock HorizontalAlignment="Left" Text="Price" Margin="0 0 12 0" />
<TextBox x:Name="txtPrice" HorizontalAlignment="Left" Height="20" Width="50" >
<interactivity:Interaction.Triggers>
<i:EventTrigger EventName="TextChanged">
<i:InvokeCommandAction Command="{Binding DataContext.UpdateAmountCommand, ElementName=root}" CommandParameter="{Binding Path=Text, ElementName=txtAmount}" />
</i:EventTrigger>
</interactivity:Interaction.Triggers>
</TextBox>
</StackPanel>
<StackPanel Orientation="Vertical">
<TextBlock HorizontalAlignment="Left" Text="Amount" Margin="0 0 12 0" />
<TextBox x:Name="txtAmount" HorizontalAlignment="Left" IsReadOnly="True">
<TextBox.Text>
<MultiBinding Converter="{StaticResource AmountConverter}">
<Binding ElementName="txtQty" Path="Text" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged"/>
<Binding ElementName="txtPrice" Path="Text" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged"/>
</MultiBinding>
</TextBox.Text>
</TextBox>
</StackPanel>
</StackPanel>
</Grid>
</Window>
VM
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.CompilerServices;
namespace WpfApp2.ViewModel
{
public class ViewModelTest : INotifyPropertyChanged
{
public ViewModelTest()
{
UpdateAmountCommand = new CustomCommand<string>(UpdateAmount, (x) => true);
}
private decimal _amount;
public decimal Amount
{
get => _amount;
set
{
if (_amount != value)
{
_amount = value;
OnPropertyChanged();
}
}
}
public CustomCommand<string> UpdateAmountCommand { get; }
private void UpdateAmount(string amountText)
{
Amount = decimal.Parse(amountText);
Debug.WriteLine(Amount);
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Not sure if there's an easier way, this is just on top of my head.
PS: You can copy the CustomCommand implementation here.
Hope this helps.
Related
I've bind the static property in DataGridRow (not DataGridTextcolumn). How can I bind it?
I already have bind a static property in a normal grid manually. Code is shown below(But now how to bind a property in DataGridRow-Wise).
<Window x:Class="Data_Grid_Row_HeaderBindingTest.GridTest"
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:Data_Grid_Row_HeaderBindingTest"
mc:Ignorable="d"
Title="GridTest" Height="450" Width="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock Text="ArgumentName" Grid.Row="0" Grid.Column="0" FontSize="25" TextAlignment="Center" Margin="10"/>
<TextBlock Text="ArgumentValue" Grid.Row="0" Grid.Column="1" FontSize="25" TextAlignment="Center" Margin="10"/>
<TextBlock Text="IA" Grid.Row="1" Grid.Column="0" FontSize="25" TextAlignment="Center" Margin="10"/>
<TextBlock Text="IB" Grid.Row="2" Grid.Column="0" FontSize="25" TextAlignment="Center" Margin="10"/>
<TextBlock Text="IC" Grid.Row="3" Grid.Column="0" FontSize="25" TextAlignment="Center" Margin="10"/>
<TextBlock Text="ID" Grid.Row="4" Grid.Column="0" FontSize="25" TextAlignment="Center" Margin="10"/>
<TextBlock Text="IE" Grid.Row="5" Grid.Column="0" FontSize="25" TextAlignment="Center" Margin="10"/>
<TextBlock Text="IF" Grid.Row="6" Grid.Column="0" FontSize="25" TextAlignment="Center" Margin="10"/>
<TextBlock Text="IG" Grid.Row="7" Grid.Column="0" FontSize="25" TextAlignment="Center" Margin="10"/>
<TextBox Text="{Binding Path=(local:Model.IA),Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" Grid.Row="1" Grid.Column="1" Width="360" Height="40"/>
<TextBox Text="{Binding Path=(local:Model.IB),Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" Grid.Row="2" Grid.Column="1" Width="360" Height="40"/>
<TextBox Text="{Binding Path=(local:Model.IC),Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" Grid.Row="3" Grid.Column="1" Width="360" Height="40"/>
<TextBox Text="{Binding Path=(local:Model.ID),Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" Grid.Row="4" Grid.Column="1" Width="360" Height="40"/>
<TextBox Text="{Binding Path=(local:Model.IE),Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" Grid.Row="5" Grid.Column="1" Width="360" Height="40"/>
<TextBox Text="{Binding Path=(local:Model.IF),Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" Grid.Row="6" Grid.Column="1" Width="360" Height="40"/>
<TextBox Text="{Binding Path=(local:Model.IG),Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" Grid.Row="7" Grid.Column="1" Width="360" Height="40"/>
<!--<DataGrid x:Name="MyGrid" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Argument Name" Binding="{Binding ArgumentName}"></DataGridTextColumn>
<DataGridTextColumn Header="Argument Value" Binding="{Binding Path=(local:Model.IA),Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
></DataGridTextColumn>
</DataGrid.Columns>
--><!--<DataGrid.RowHeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type DataGridRow}},
Path=Item.Row.Header}"/>
</DataTemplate>
</DataGrid.RowHeaderTemplate>--><!--
<DataGrid.RowHeaderStyle>
<Style TargetType="DataGridRowHeader">
<Setter Property="Content" Value="{Binding Path=(local:Model.Name)}"/>
</Style>
</DataGrid.RowHeaderStyle>
</DataGrid>-->
</Grid>
</Window>
Model Class
using System;
using System.Collections.Generic;
using System.Text;
namespace Data_Grid_Row_HeaderBindingTest
{
public class Model
{
private static int iA;
private static int iB;
private static int iC;
private static int iD;
private static int iE;
private static int iF;
private static int iG;
private static string name;
public static int IA
{
get
{
return iA;
}
set
{
iA = value;
// Raise a change event
OnFilterStringChanged(EventArgs.Empty);
}
}
public static int IB
{
get
{
return iB;
}
set
{
iB = value;
// Raise a change event
OnFilterStringChanged(EventArgs.Empty);
}
}
public static int IC
{
get
{
return iC;
}
set
{
iC = value;
// Raise a change event
OnFilterStringChanged(EventArgs.Empty);
}
}
public static int ID
{
get
{
return iD;
}
set
{
iD = value;
// Raise a change event
OnFilterStringChanged(EventArgs.Empty);
}
}
public static int IE
{
get
{
return iE;
}
set
{
iE = value;
// Raise a change event
OnFilterStringChanged(EventArgs.Empty);
}
}
public static int IF
{
get
{
return iF;
}
set
{
iF = value;
// Raise a change event
OnFilterStringChanged(EventArgs.Empty);
}
}
public static int IG
{
get
{
return iG;
}
set
{
iG = value;
// Raise a change event
OnFilterStringChanged(EventArgs.Empty);
}
}
public static string Name
{
get
{
return name;
}
set
{
name = value;
// Raise a change event
OnFilterStringChanged(EventArgs.Empty);
}
}
// Declare a static event representing changes to your static property
public static event EventHandler FilterStringChanged;
// Raise the change event through this static method
protected static void OnFilterStringChanged(EventArgs e)
{
EventHandler handler = FilterStringChanged;
if (handler != null)
{
handler(null, e);
}
}
static Model()
{
// Set up an empty event handler
FilterStringChanged += (sender, e) => { return; };
}
}
}
Attached image:
But now the same property bind to DataGrid. How can I bind?
I think what you ware looking for is MultiBinding:
<Window x:Class="MultiBinding.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:MultiBinding"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<local:NameList x:Key="NameListData"/>
<local:NameConverter x:Key="MyNameConverter"/>
<DataTemplate x:Key="NameItemTemplate">
<TextBlock>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource MyNameConverter}">
<Binding Path="FirstName"/>
<Binding Path="LastName"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Width" Value="120"/>
<Setter Property="Background" Value="Silver"/>
<Setter Property="HorizontalAlignment" Value="Center"/>
</Style>
</Window.Resources>
<StackPanel>
<TextBlock FontSize="18" FontWeight="Bold" Margin="10"
Background="White" Width="Auto">MultiBinding Sample</TextBlock>
<ListBox Width="200"
ItemsSource="{Binding Source={StaticResource NameListData}}"
ItemTemplate="{StaticResource NameItemTemplate}"
IsSynchronizedWithCurrentItem="True"/>
<TextBlock Padding="0,20,0,0" FontSize="11" Background="White">Normal Format:</TextBlock>
<TextBlock Name="textBox1" DataContext="{StaticResource NameListData}">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource MyNameConverter}"
ConverterParameter="FormatNormal">
<Binding Path="FirstName"/>
<Binding Path="LastName"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
<TextBlock Padding="0,20,0,0" FontSize="11" Background="White">Last Name First Format:</TextBlock>
<TextBlock Name="textBox2" DataContext="{StaticResource NameListData}">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource MyNameConverter}"
ConverterParameter="FormatLastFirst">
<Binding Path="FirstName"/>
<Binding Path="LastName"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</StackPanel>
</Window>
Here is the NameConverter:
// // Copyright (c) Microsoft. All rights reserved.
// // Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using System.Globalization;
using System.Windows.Data;
namespace MultiBinding
{
public class NameConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
string name;
switch ((string) parameter)
{
case "FormatLastFirst":
name = values[1] + ", " + values[0];
break;
default:
name = values[0] + " " + values[1];
break;
}
return name;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
var splitValues = ((string) value).Split(' ');
return splitValues;
}
}
}
NameList:
// // Copyright (c) Microsoft. All rights reserved.
// // Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System.Collections.ObjectModel;
namespace MultiBinding
{
public class NameList : ObservableCollection<PersonName>
{
public NameList()
{
Add(new PersonName("Willa", "Cather"));
Add(new PersonName("Isak", "Dinesen"));
Add(new PersonName("Victor", "Hugo"));
Add(new PersonName("Jules", "Verne"));
}
}
}
PersonName:
// // Copyright (c) Microsoft. All rights reserved.
// // Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace MultiBinding
{
public class PersonName
{
public PersonName(string first, string last)
{
FirstName = first;
LastName = last;
}
public string FirstName { get; set; }
public string LastName { get; set; }
}
}
Here is a link to the full source code, just go into the Samples->Data Binding->MultiBinding project in the WPFSamples.sln:
https://drive.google.com/drive/folders/0BxL9JBEAEaAUcjlxNWdzU3h6bjA?resourcekey=0-N-piwEgxrfTw4CT-4cnEqA&usp=sharing
For details please see:
https://learn.microsoft.com/en-us/previous-versions/ms771633(v=vs.100)?redirectedfrom=MSDN
I've nested menuitems bounded to observable collection named 'CollectionOfAuthors'.
Here's the MenuItem Hierarchy:
Author -->AuthorName1-->BookName1,BookName2,BookName3
Author is TopLevelMenuItem which opens in list of Author names such that each Author name opens into list of Books.
While Clicking on each BookName menuitem through NavigateToBook command, I want to send the BookName, AuthorName and AuthorID to ViewModel as command parameters,
But I am finding empty values as (DependencyProperty.UnsetValue) passed to ViewModel.
Need to know what correction is required?
View.xaml
<Menu>
<MenuItem Header="Authors" x:Name="TopLevelMenuItem"
ItemsSource="{Binding CollectionOfAuthors, Mode=TwoWay}">
<in:Interaction.Triggers>
<in:EventTrigger EventName="PreviewMouseLeftButtonDown">
<in:InvokeCommandAction Command="{Binding DataContext.RefreshAuthorsList,RelativeSource={RelativeSource AncestorType=Menu}}"/>
</in:EventTrigger>
</in:Interaction.Triggers>
<MenuItem.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=Books}">
<StackPanel>
<TextBlock x:Name="tbAuthor" Text="{Binding AuthorName}"/>
<TextBlock x:Name="tbAuthorID" Text="{Binding AuthorID}" Visibility="Collapsed"/>
</StackPanel>
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<TextBlock x:Name="tbBookName" Text="{Binding}">
<TextBlock.InputBindings>
<MouseBinding Command="{Binding DataContext.NavigateToBook, RelativeSource={RelativeSource AncestorType=Menu}}" MouseAction="LeftClick" >
<MouseBinding.CommandParameter>
<MultiBinding Converter="{StaticResource MultiCommandConverter}">
<Binding Path="Text" ElementName="tbBookName"/>
<Binding Path="DataContext.AuthorName" RelativeSource="{RelativeSource AncestorLevel=2, AncestorType=MenuItem}" />
<Binding Path="DataContext.AuthorID" RelativeSource="{RelativeSource AncestorLevel=2, AncestorType=MenuItem}" />
</MultiBinding>
</MouseBinding.CommandParameter>
</MouseBinding>
</TextBlock.InputBindings>
</TextBlock>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</MenuItem.ItemTemplate>
</MenuItem>
</Menu>
ViewModel.cs
public ICommand NavigateToBook
{
get { return new DelegateCommand(NavigateToBookExecute); }
}
private void NavigateToBookExecute(object obj)
{
string selectedBookName = ((object[])obj)[0].ToString();
string selectedAuthorName = ((object[])obj)[1].ToString();
string selectedAuhorID = ((object[])obj)[2].ToString();
}
public ICommand RefreshAuthorsList
{
get { return new DelegateCommand(RefreshAuthorsListExecute); }
}
private void RefreshAuthorsListExecute(object m)
{
CollectionOfAuthors = new ObservableCollection<Author>();
//Here AuthorDetails is another global collection which gets loaded during constructor call
foreach (var objAuthorItem in AuthorDetails)
{
CollectionOfAuthors.Add(new Author
{
AuthorName = objAuthorItem.DisplayName,
Books = objAuthorItem.ListOfBooks,
AuthorID = objAuthorItem.Id,
});
}
}
private ObservableCollection<Author> _collectionOfAuthors;
public ObservableCollection<Author> CollectionOfAuthors
{
get { return _collectionOfAuthors; }
set { SetProperty(ref _collectionOfAuthors, value); }
}
Author.cs
public class Author
{
public string AuthorName { get; set; }
public string AuthorID { get; set; }
List<string>Books = new List<string>();
}
MultiCommandConverter.cs
public class MultiCommandConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return values.Clone();
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Since you have Command at top level menu item, this Command will try to call even before your inner Command, no mather what event should trigger it.
As Workaround you could pass IsSubmenuOpen property of TopMenuItem as CommandParameter, and check if Menu is opened, and then in Command's execute action you could check if menu is Opened then continue or return. This will stop your items from being refreshed.
CallStack of your command is:
Click on book menuItem
RefreshListCommand runs
items are being refreshed, old ones are removed
Binding is trying to get properites from just removed items
Sample solution:
View.xaml
<Menu>
<MenuItem Header="Authors" Background="Red" x:Name="TopLevelMenuItem"
ItemsSource="{Binding CollectionOfAuthors, Mode=TwoWay}">
<in:Interaction.Triggers>
<in:EventTrigger EventName="PreviewMouseLeftButtonDown">
<in:InvokeCommandAction Command="{Binding DataContext.RefreshAuthorsList,RelativeSource={RelativeSource AncestorType=Menu}}" CommandParameter="{Binding IsSubmenuOpen , ElementName=TopLevelMenuItem}"/>
</in:EventTrigger>
</in:Interaction.Triggers>
<MenuItem.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=Books}">
<StackPanel DataContext="{Binding}">
<TextBlock x:Name="tbAuthor" Text="{Binding AuthorName}"/>
<TextBlock x:Name="tbAuthorID" Text="{Binding AuthorID}" Visibility="Collapsed"/>
</StackPanel>
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<TextBlock x:Name="tbBookName" DataContext="{Binding}" Text="{Binding}">
<in:Interaction.Triggers>
<in:EventTrigger EventName="MouseDown">
<in:InvokeCommandAction Command="{Binding DataContext.NavigateToBook, RelativeSource={RelativeSource AncestorType=Menu}}" >
<in:InvokeCommandAction.CommandParameter>
<MultiBinding Converter="{StaticResource MultiCommandConverter}">
<Binding Path="Text" ElementName="tbBookName"/>
<Binding Path="DataContext.AuthorName" RelativeSource="{RelativeSource AncestorLevel=1, AncestorType=StackPanel}" />
<Binding Path="DataContext.AuthorID" RelativeSource="{RelativeSource AncestorLevel=1, AncestorType=StackPanel}" />
</MultiBinding>
</in:InvokeCommandAction.CommandParameter>
</in:InvokeCommandAction>
</in:EventTrigger>
</in:Interaction.Triggers>
</TextBlock>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</MenuItem.ItemTemplate>
</MenuItem>
</Menu>
And then in your RefreshAuthorsListExecute
private void RefreshAuthorsListExecuteExecute(object m)
{
if ((bool)m)
return;
I have 3 TextBoxes bind with my class(Transaction) properties like this
<TextBox Text="{Binding Path=Transaction.Bills100,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" Name="bills100" Grid.Column="2" Grid.Row="1" Margin="7"></TextBox>
<TextBox Text="{Binding Path=Transaction.Bill50,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" Name="bills50" Grid.Column="2" Grid.Row="2" Margin="7"></TextBox>
<TextBox Text="{Binding Path=Transaction.Bill20,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" Name="bills20" Grid.Column="2" Grid.Row="3" Margin="7"></TextBox>
Also I have another TextBox where I have done multibinding and done addition of the first three Textboxes like
<TextBox Grid.Column="2" IsReadOnly="True" Grid.Row="7" Grid.ColumnSpan="2" Margin="7" Name="TotalBills">
<TextBox.Text>
<MultiBinding Converter="{ikriv:MathConverter}" ConverterParameter="x+y+z" Mode="TwoWay">
<Binding Path="Text" ElementName="bills100" />
<Binding Path="Text" ElementName="bills50" />
<Binding Path="Text" ElementName="bills20" />
</MultiBinding>
</TextBox.Text>
</TextBox>
I want to bind this multibinding textbox with my class(Transaction) with property as Transaction.Total like my first three textboxes but it shows error
Property text is set more than once
Actually we cannot get the value of a two-way binding from one property and then set the value of another property.
Finally I came with a solution like this
In my Class Transaction
private double _totalBills;
public double TotalBills
{
get { return _totalBills; }
set { _totalBills= value; Notify("TotalBills"); }
}
In XAML(Instead of Multibinding)
<TextBox Text="{Binding Path=Transaction.TotalBills,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" Grid.Column="2" IsReadOnly="True" Grid.Row="7" Grid.ColumnSpan="2" Margin="7" Name="TotalBills"/>
My ViewModel
public class MainViewModel: INotifyPropertyChanged
{
private Transaction _transactionDetails;
public MainViewModel()
{
Transaction= new Transaction();
_transactionDetails.PropertyChanged += _transactionDetails_PropertyChanged;
}
private void _transactionDetails_PropertyChanged(object sender,PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case "TotalBills":
_calculate(); //My method for calculation
break;
}
}
}
So am testing multibinding in wpf and i have three text boxes which should get the year,month,day and my converter class should return a date with those inputs..pretty simple.
But in my convert method the values[0] is always unset that is i am always getting Dependencyproperty.UnsetValue even if get give it an initial value.
XAML
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:s="clr-namespace:System;assembly=mscorlib"
xmlns:src="clr-namespace:WpfApplication2"
Title="MultiBinding Demo" Width="200" Height="200">
<Window.Resources>
<src:DateConverter x:Key="myConverter" />
</Window.Resources>
<StackPanel Orientation="Horizontal">
<StackPanel.Resources>
</StackPanel.Resources>
<TextBox Name="tb1" Margin="10" Width="Auto" Height="20"></TextBox>
<TextBox Name="tb2" Margin="10" Width="20" Height="20" ></TextBox>
<TextBox Name="tb3" Width="20" Height="20" ></TextBox>
<Label Name="Date" Width="50" Height="25" Margin="5" >
<Label.Content>
<MultiBinding Converter="{StaticResource myConverter}" Mode="OneWay">
<Binding ElementName="tbl" Path="Text" />
<Binding ElementName="tb2" Path="Text" />
<Binding ElementName="tb3" Path="Text" />
</MultiBinding>
</Label.Content>
</Label>
</StackPanel>
DATECONVERTER CLASS
class DateConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (values[0] == DependencyProperty.UnsetValue || values[1] == DependencyProperty.UnsetValue || values[2] == DependencyProperty.UnsetValue)
{
return "";
}
else
{
int year = (int)values[0];
int month = (int)values[1];
int day = (int)values[2];
DateTime date = new DateTime(year, month, day);
return date.ToShortDateString();
}
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
I'm looking at your XAML, and it looks like your first TextBox is named tb1 (number 1) but in the binding you're referencing element name tbl (letter L).
Trying to determine if it is possible to bind the SelectedValue of a ComboBox to the inputs of multiple ObjectDataProviders with XAMAL Bindings.
I looked at MultiBinding but that appears to be grouping multiple controls together, not exactly what I'm looking to day.
I'd like to be able to have the ComboBox (locations) change the TextBlock (deviance) which it does AND to call the ObjectDataProvider (CommentProvider) to update the TextBox (locationComments).
This is fairly straightforward in a code-behind but would prefer to not go this route as a learning experience.
XAMAL CODE
<Window.Resources>
<ObjectDataProvider x:Key="LocationProvider"
ObjectType="{x:Type srv:ServiceClient}"
IsAsynchronous="True"MethodName="GetAssignedLocations" />
<ObjectDataProvider
x:Key="DevianceProvider"
ObjectType="{x:Type srv:ServiceClient}"
IsAsynchronous="True" MethodName="GetPercentChange">
<ObjectDataProvider.MethodParameters>
<system:String>Location1</system:String>
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
<ObjectDataProvider
x:Key="CommentProvider"
ObjectType="{x:Type srv:ServiceClient}"
IsAsynchronous="True"
MethodName="GetCommentByBusinessUnit">
<ObjectDataProvider.MethodParameters>
<system:String>Location1</system:String>
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
</Window.Resources>
<ComboBox Height="23" HorizontalAlignment="Left" Margin="12,12,0,0" Name="locations" VerticalAlignment="Top" ItemsSource="{Binding Source={StaticResource LocationProvider}}"
DisplayMemberPath="BuName" SelectedValuePath="BuKey"
SelectionChanged="locations_SelectionChanged">
<ComboBox.SelectedValue>
<Binding Source="{StaticResource DevianceProvider}"
Path="MethodParameters[0]"
BindsDirectlyToSource="True"
Mode="OneWayToSource" />
</ComboBox.SelectedValue>
<TextBlock Name="deviance" Height="23" Margin="0,0,645,17" Width="40" Text="{Binding Source={StaticResource DevianceProvider}}" IsEnabled="False" />
<TextBox Height="23" Margin="0,0,181,17" Name="locationComments" Width="350" />
You're on the right track with the MultiBinding.
The key is to use a MultiValueCoverter in conjunction with the MultiBinding.
<MultiBinding Converter="{StaticResource Coverter_LocationMultiConverter}"
Mode="OneWayToSource">
<Binding Source="{StaticResource DevianceProvider}"
Path="MethodParameters[0]"
BindsDirectlyToSource="True"
Mode="OneWayToSource" />
<Binding Source="{StaticResource CommentProvider}"
Path="MethodParameters[0]"
BindsDirectlyToSource="True"
Mode="OneWayToSource" />
</MultiBinding>
Where we were binding to just one thing before, now we are binding it to both ObjectDataProviders. The key factor that lets us do this is the converter:
public class LocationMultiCoverter : IMultiValueConverter
{
#region IMultiValueConverter Members
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
return new object[] { value, value };
}
#endregion
}
Because we just need the same value in both places the CovertBack method is quite simple, however I'm sure you can see that it could be used to parse some complex stuff and pass back different components to different places in the UI.
Using this converter we can also try out a small sample, using two text boxes instead:
<Window x:Class="Sample.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Sample"
Title="Window1"
Height="300"
Width="300">
<Window.Resources>
<local:LocationMultiCoverter x:Key="Coverter_LocationMultiConverter" />
</Window.Resources>
<Grid>
<StackPanel>
<TextBlock x:Name="uiDeviance" />
<TextBlock x:Name="uiComment" />
<ComboBox x:Name="uiLocations"
Height="23"
HorizontalAlignment="Left"
VerticalAlignment="Top"
SelectedValuePath="Content">
<ComboBoxItem>1</ComboBoxItem>
<ComboBoxItem>2</ComboBoxItem>
<ComboBoxItem>3</ComboBoxItem>
<ComboBoxItem>4</ComboBoxItem>
<ComboBoxItem>5</ComboBoxItem>
<ComboBox.SelectedValue>
<MultiBinding Converter="{StaticResource Coverter_LocationMultiConverter}"
Mode="OneWayToSource">
<Binding ElementName="uiDeviance"
Path="Text"
BindsDirectlyToSource="True" />
<Binding ElementName="uiComment"
Path="Text"
BindsDirectlyToSource="True" />
</MultiBinding>
</ComboBox.SelectedValue>
</ComboBox>
</StackPanel>
</Grid>
(The Converter in my example exists in the Window's code behind as a separate class)
And as you can see testing this out it will update both TextBoxes when the SelectedValue changes.