Pass current item to ValueConverter - c#

I have a ListBox where I display all order positions. I need to display a price. I created a ValueConverter which takes a OrderPosition object and returns my price as double.
Formula: Amount * Product.Price (Amount and Product are properties in OrderPosition)
My XAML just won't display anything:
<ListBox Grid.Row="1" Grid.Column="0" Margin="3" ItemsSource="{Binding SelectedOrder.OrderPositions}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0">
<TextBlock.Text>
<MultiBinding StringFormat="{}{0}x {1}">
<Binding Path="Amount" />
<Binding Path="Product.Label" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
<TextBlock Text="{Binding /, Converter={StaticResource PositionPriceConverter}, StringFormat={}{0:c}}" Grid.Column="1"
TextAlignment="Right" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Here is my converter:
public class PositionPriceConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var position = (OrderPosition)value;
return position.Amount * position.Product.Price;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}

At the moment you set Path=/ which binds it to CollectionView.CurrentItem
When the source is a collection view, the current item can be specified with a slash (/). For example, the clause Path=/ sets the binding to the current item in the view. When the source is a collection, this syntax specifies the current item of the default collection view.
You can achieve what you're after by setting Path=. or not setting Path altogether.
<TextBlock Text="{Binding Path=., Converter=...}
or
<TextBlock Text="{Binding Converter=...}
but be aware that it will not trigger update when either Amount or Product.Price will change so maybe MultiBinding and IMultiValueConverter would be better option.

I Am not sure if that path you provided to the binding is legal ({Binding /, Converter....).
try to change it in:
<TextBlock Text="{Binding Converter={StaticResource PositionPriceConverter}, StringFormat={}{0:c}}" Grid.Column="1" TextAlignment="Right" />
or
<TextBlock Text="{Binding Path=., Converter={StaticResource PositionPriceConverter}, StringFormat={}{0:c}}" Grid.Column="1" TextAlignment="Right" />

<ListBox Grid.Row="1"
Grid.Column="0"
Margin="3"
ItemsSource="{Binding SelectedOrder.OrderPositions}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0">
<TextBlock.Text>
<MultiBinding StringFormat="{}{0}x {1}">
<Binding Path="Amount" />
<Binding Path="Product.Label" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
<TextBlock Grid.Column="1">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource PositionPriceConverter}" StringFormat="{}{0}x {1}">
<Binding Path="Amount" />
<Binding Path="Product.Label" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Change the converter like this,
public class PositionPriceConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
var amt = (double)values[0];
var price = (double) values[1];
return amt * price;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}

Related

Using MVVM instead of MultiValueConverter in WPF XAML

I've been working with XAML for some time now, but when I started I didn't knew much about MVVM so I avoided it. So all the logic in my projects is written in .xaml.cs instead of ViewModel.cs. Now the project is big and I have to move some parts of it to MVVM per request and I have no clue how to do so or where to actually start.
about the problem:
I have two Entities. lets call them A and Secret.
A can have Secret. If A does have it I need to display Secret.Name and Secret.Description beside the A data.
They are binded together with: A.Secret == Secret.Id.
A data and B data can change independently and is received/changed over API calls, but A.Secret and Secret.Id bind will never change.
Secrets is just an array of SecretModel elements
<ListBox ItemsSource="{Binding A, ElementName=uc}" SelectedItem="{Binding SelectedA, ElementName=uc}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid VerticalAlignment="Center">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto" MaxHeight="300"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="ID:" TextAlignment="Right" />
<TextBlock Grid.Column="1" Text="{Binding Id}"/>
<TextBlock Grid.Row="1" Text="Type:" TextAlignment="Right"/>
<TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding Type}" />
<TextBlock Grid.Row="2" Text="Secret:" TextAlignment="Right" Visibility="{Binding Secret, Converter={StaticResource StringToVisibilityConverter}}"/>
<TextBlock Grid.Row="2" Grid.Column="1" Visibility="{Binding Secret, Converter={StaticResource StringToVisibilityConverter}}">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource SecretIdToNameConverter}">
<Binding Path="Secret"/>
<Binding Path="Secrets" ElementName="uc"/>
</MultiBinding>
</TextBlock.Text>
<TextBlock.ToolTip>
<MultiBinding Converter="{StaticResource SecretIdToDescriptionConverter}">
<Binding Path="Secret"/>
<Binding Path="Secrets" ElementName="uc" />
</MultiBinding>
</TextBlock.ToolTip>
</TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Converters looks like this.
public class SecretIdToDescriptionConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values[0] != null && values[0] != DependencyProperty.UnsetValue && values[0] is string SecretId && values[1] != null && values[1] != DependencyProperty.UnsetValue && values[1] is SecretModel[] Secrets)
{
var tmp = Secrets.FirstOrDefault(secret => secret?.Id == SecretId);
if (tmp != null)
return tmp.Description;
return null;
}
else
{
return null;
}
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
If anyone could give me some help/pointers on how I should/could migrate from Converters of this type to MVVM or point me to some resource explaining it to me - that would be greatly appreciated.
The things that mostly boggles me:
How should I share/acces the data betwean .xaml.cs or .xaml and ViewModel.cs at this point?
How are calls made to retrieve/give such data?
Is it possible to create method that would return the corresponding SecretModel, so I could just use it after the call in .xaml?
like
<magic ItemsSource= getSecret(A.Secret) >
<TextBlock Text = {Binding Name}/>
</magic>

How do I bind combobox IsChecked to 4 buttons from the same group?

I want the combo box to be enable when pressing one of the radio buttons.
<RadioButton x:Name="A" GroupName="rButton" Content="A" Grid.Column="4"/>
<RadioButton x:Name="B" GroupName="rButton" Content="B" Grid.Column="4"/>
<RadioButton x:Name="C" GroupName="rButton" Content="C" Grid.Column="4"/>
<RadioButton x:Name="D" GroupName="rButton" Content="D" Grid.Column="4"/>
<ComboBox IsEnabled="{Binding IsChecked,?? }" Grid.Column="5" Width="120" Height="30"/>
If you want to solve this via Bindings (and you should), you need a MultiBindingConverter that returns true as long as one of the values is true (boolean OR):
public class BooleanOrConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
foreach (object value in values)
{
if (value is bool && (bool) value)
return true;
}
return false;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
return Enumerable.Repeat(DependencyProperty.UnsetValue, targetTypes.Length).ToArray();
}
}
Definition:
<Window.Resources>
<local:BooleanOrConverter x:Key="OrConverter"/>
</Window.Resources>
Usage:
<RadioButton x:Name="RadioButtonSource" GroupName="rButton" Content="A" Grid.Column="4"/>
<RadioButton x:Name="RadioButtonToken" GroupName="rButton" Content="B" Grid.Column="4"/>
<RadioButton x:Name="RadioButtonII" GroupName="rButton" Content="C" Grid.Column="4"/>
<RadioButton x:Name="RadioButtonUkey" GroupName="rButton" Content="D" Grid.Column="4"/>
<ComboBox Grid.Column="5" Width="120" Height="30">
<ComboBox.IsEnabled>
<MultiBinding Converter="{StaticResource OrConverter}">
<Binding ElementName="RadioButtonSource" Path="IsChecked"/>
<Binding ElementName="RadioButtonToken" Path="IsChecked"/>
<Binding ElementName="RadioButtonII" Path="IsChecked"/>
<Binding ElementName="RadioButtonUkey" Path="IsChecked"/>
</MultiBinding>
</ComboBox.IsEnabled>
</ComboBox>
This way, as soon as any of the RadioButtons's IsChecked properties becomes true, the ComboBox is enabled. If you reset the RadioButtons, it get's disabled again.

Grouping ListBox with Date

I have no idea how to achieve this, but I have a date and time column in a ListBox. The Date column should not display if the date was already in there. I know that in combination with ListCollectionView and Listview/DataGrids, it is propably possible. But can I achieve this with a ListBox and a List. Keep in mind I am using the MVVM principle. This is my listbox:
<ListBox Grid.Row="2" ScrollViewer.HorizontalScrollBarVisibility="Disabled" ItemsSource="{Binding Schedules}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Background="Transparent">
<Grid Background="Transparent">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="100"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock Grid.Column="0" Grid.Row="0" Text="{Binding MyDateTime, StringFormat='d' }" HorizontalAlignment="Left" VerticalAlignment="Center"/>
<TextBlock Grid.Column="1" Grid.Row="0" Text="{Binding MyDateTime, StringFormat='t'}" HorizontalAlignment="Left" VerticalAlignment="Center"/>
<TextBlock Grid.Column="2" Grid.Row="0" Text="{Binding SomeText}" TextTrimming="WordEllipsis" LineStackingStrategy="MaxHeight" MaxHeight="20" HorizontalAlignment="Left" VerticalAlignment="Center"/>
</Grid>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I have made this in excel to give an example of what I am trying to achieve:
I want the affect where I have highlighted with yellow
Converters
// Order Schedules using System.Linq
public class ToOrderedListConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
List<ScheduleItem> schedules = (List<ScheduleItem>)value;
var subset = from item in schedules
orderby item.MyDateTime.TimeOfDay
orderby item.MyDateTime.ToString("yyyy/MM/dd") descending
select item;
return subset.ToList();
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
// Show only first occurrence of date
public class DateToVisibilityConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
DateTime currentItem = (DateTime)values[0];
List<ScheduleItem> schedules = (List<ScheduleItem>)values[1];
ScheduleItem firstOccurrence =
schedules.Find(item => item.MyDateTime.Year == currentItem.Year
&& item.MyDateTime.Month == currentItem.Month
&& item.MyDateTime.Day == currentItem.Day);
if (firstOccurrence.MyDateTime == currentItem)
return Visibility.Visible;
else return Visibility.Collapsed;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
XAML
<ListBox Grid.Row="2" ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ItemsSource="{Binding Schedules, Converter={StaticResource ToOrderedListConverter}}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Background="Transparent">
<Grid Background="Transparent">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="100"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock Grid.Column="0" Grid.Row="0" Text="{Binding MyDateTime, StringFormat='dd/MM/yyyy'}" HorizontalAlignment="Left" VerticalAlignment="Center">
<TextBlock.Visibility>
<MultiBinding Converter="{StaticResource DateToVisibilityConverter}">
<Binding Path="MyDateTime"/>
<Binding RelativeSource="{RelativeSource AncestorType={x:Type ListBox}}"
Path="ItemsSource"/>
</MultiBinding>
</TextBlock.Visibility>
</TextBlock>
<TextBlock Grid.Column="1" Grid.Row="0" Text="{Binding MyDateTime, StringFormat='t'}" HorizontalAlignment="Left" VerticalAlignment="Center"/>
<TextBlock Grid.Column="2" Grid.Row="0" Text="{Binding SomeText}" TextTrimming="WordEllipsis" LineStackingStrategy="MaxHeight" MaxHeight="20" HorizontalAlignment="Left" VerticalAlignment="Center"/>
</Grid>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
You can use this approach for your requirements.
https://code.msdn.microsoft.com/windowsdesktop/CollectionView-Tips-MVVM-d6ebb4a7#content

How to highlight ListBox Items matching a certain condition

I have 2 ListBoxes defined thus:
<ListBox Name="aggregatesListBox" SelectionChanged="aggregatesList_SelectionChanged">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Amount}"/>
<TextBlock Text="{Binding Path=AccountId}"/>
<TextBlock Text="{Binding Path=Name}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<ListBox Name="postingsListBox" >
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=PostingId}" />
<TextBlock Text="{Binding Path=Amount}" />
<TextBlock Text="{Binding Path=CreatedDate}" />
<TextBlock Text="{Binding Path=AccountId}" />
<TextBlock Text="{Binding Path=Name}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I want all items in the postings list to be highlighted (in some way, preferably background colour) if they share the same Account Id as the currently selected aggregated item.
What are my options?
On the advice given I have modified as follows
<ListBox Name="postingsListBox" >
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<StackPanel.Resources>
<local:IdToBrushConverter x:Key="IdToBrushConverter" />
</StackPanel.Resources>
<StackPanel.Background>
<MultiBinding Converter="{StaticResource IdToBrushConverter}">
<Binding ElementName="aggregatesListBox" Path="SelectedItem.AccountId"/>
<Binding Path="AccountId"/>
</MultiBinding>
</StackPanel.Background>
<TextBlock Text="{Binding Path=PostingId}" />
<TextBlock Text="{Binding Path=Amount}" />
<TextBlock Text="{Binding Path=CreatedDate}"/>
<TextBlock Text="{Binding Path=AccountId}" />
<TextBlock Text="{Binding Path=Name}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
and
public class IdToBrushConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
System.Windows.Media.Color colour;
if (values[0] == DependencyProperty.UnsetValue || values[1] == DependencyProperty.UnsetValue || ((int)values[0] != (int)values[1]))
colour = System.Windows.Media.Colors.White;
else
colour = System.Windows.Media.Colors.CornflowerBlue;
return new SolidColorBrush(colour);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException("");
}
}
An attribute on the Window is required so that the brush converter can be located
xmlns:local="clr-namespace:MyAccountingThing"
I also changed the behind the scenes logic to use a list of Objects as the ItemsSource of each of the 2 Listboxes rather than the DataRowView I had previously.
Sorted - Thanks!
You could use a multibinding with a converter, here's an example.
XAML
<ListBox x:Name="list1"
ItemsSource="{Binding List1}">
</ListBox>
<ListBox x:Name="list2"
ItemsSource="{Binding List2}"
Grid.Column="2">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding .}">
<TextBlock.Background>
<MultiBinding Converter="{StaticResource converter}">
<Binding Path="SelectedItem" ElementName="list1"/>
<Binding Path="."/>
</MultiBinding>
</TextBlock.Background>
</TextBlock>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
On my silly example, I used the MultiBiding to be able to pass more than one parameter to the Converter, which is the selectedItem on the list1 and the currentItem that ListBox2 is applying the Template, next, I used the converter to compare the received values:
Converter:
public class Converter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var selectedValueList1 = values[0];
var currentItemList2 = values[1];
if(selectedValueList1 == null) // Listbox 1 has no selected Item
return Brushes.Black;
if (selectedValueList1 == currentItemList2)
return Brushes.Red;
return Brushes.Transparent;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
And of course, you have to elaborate better the test on your converter, in my example I just pass two strings to be compared.
And that is it, it works like expected.

wpf binding max width proportional to other element's width

I have the following ItemTemplate:
<ListView.ItemTemplate>
<DataTemplate>
<Border CornerRadius="5" Background="{Binding SenderId, Converter={StaticResource ColorConverter}}"
MaxWidth="{Binding ActualWidth, ElementName=SideBar}"
HorizontalAlignment="{Binding SenderId, Converter={StaticResource AlignmentConverter}}">
<TextBlock Text="{Binding Message}" TextWrapping="Wrap" />
</Border>
</DataTemplate>
</ListView.ItemTemplate>
What I would like to do is to bind the Border elements MaxWidth property to 70% of the SideBar's Width but I can't seem to find a way to do this in xaml.
Is there a clean way to achieve this?
To expand on Rohit Vats comment:
Add this class to your solution:
public class ElementSizeConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
double percentage = double.Parse(parameter.ToString());
return double.Parse(value.ToString()) * percentage;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return null;
}
}
Then, in your XAML, declare the namespace of where this class is in the document header:
xmlns:ConverterNamespace="clr-namespace:ConverterNamespace"
Instantiate the converter in the resources:
<Window.Resources>
<ConverterNamespace:ElementSizeConverter x:Key="ElementSizeConverter"/>
</Window.Resources>
And then you can use the following binding:
Width="{Binding Width, ElementName=elementName, Converter={StaticResource ElementSizeConverter}, ConverterParameter=0.7}"
Note: I couldn't get ActualWidth to work, but play around with this and see if this works for you.
By nesting in a grid you could also get the percentages:
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="15*" />
<ColumnDefinition Width="70*" />
<ColumnDefinition Width="15*" />
</Grid.ColumnDefinitions>
<Border Grid.Column="1"
CornerRadius="5"
Background="{Binding SenderId, Converter={StaticResource ColorConverter}}"
HorizontalAlignment="{Binding SenderId, Converter={StaticResource AlignmentConverter}}">
<TextBlock Text="{Binding Message}" TextWrapping="Wrap" />
</Border>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>

Categories