Caliburn.Micro - Binding ObservableCollection of ValueTuple to ComboBox - c#

I'm trying to bind ObservableCollection of ValueTuples to ComboBox in WPF using Caliburn.Micro framework MVVM. When I do that in ViewModel:
private ObservableCollection<Tuple<string, string>> databasesFromDisk;
public ObservableCollection<Tuple<string, string>> DatabasesFromDisk
{
get => databasesFromDisk;
set
{
databasesFromDisk = value;
NotifyOfPropertyChange(() => DatabasesFromDisk);
}
}
and in XAML View:
<ComboBox x:Name="DatabasesFromDisk" DisplayMemberPath="Item1"/>
it works, ComboBox fills with first strings. But when I try to use C# 7 and change to:
private ObservableCollection<(string name, string path)> databasesFromDisk;
public ObservableCollection<(string name, string path)> DatabasesFromDisk
{
get => databasesFromDisk;
set
{
databasesFromDisk = value;
NotifyOfPropertyChange(() => DatabasesFromDisk);
}
}
it doesn't work when I don't change XAML - it shows empty list. It doesn't work when I change to DisplayMemberPath="name" - the same empty list. And it doesn't work properly when I remove DisplayMemberPath - it shows whole list but with both strings concatenated.
How can I do it with ValueTuples?

Before C# 7 all the Items of Tuple are properties which are bindable. In C# 7 ValueTuple are fields which are not bindable.
https://msdn.microsoft.com/en-us/library/dd386940(v=vs.110).aspx
https://github.com/dotnet/corefx/blob/master/src/System.ValueTuple/src/System/ValueTuple/ValueTuple.cs#L291
One of the possible solution can be using the IValueConverter
public class ValueTupleConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var tuple = value as (string name, string path)?;
if (tuple == null)
return null;
return tuple.Value.Name;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
<ComboBox x:Name="DatabasesFromDisk">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={StaticResource ValueTupleConverter}}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>

Related

WPF Treeview use property value as Binding Path

I'm trying to create a Treeview using an ObservableCollection of a custom class called MachineComponentFault, which includes a string property called FaultText, and I'd like to make the text localized.
I'm using WPF Runtime Localization from Codeproject to localize texts at runtime, and it usually works as follows:
<TextBlock Text="{Binding Path=NameInResources, Source={StaticResource Resources}}"/>
The problem is that I can't seem to figure out how to set the value of the property to the path, so that it can retrieve the translation. This is what I managed thus far:
<TreeView Name="myTreeView" VirtualizingPanel.IsVirtualizing="True" ItemsSource="{Binding Faults}">
<TreeView.Resources>
<DataTemplate DataType="{x:Type MassComponents:MachineComponentFault}">
<StackPanel Orientation="Horizontal" >
<TextBlock Name="Text1" Text="{Binding FaultText}"/>
<TextBlock Name="Text2" Text="{Binding Path=FLT_PTC_1, Source={StaticResource Resources}}"/>
</StackPanel>
</DataTemplate>
</TreeView.Resources>
</TreeView>
Essentially Text1 shows FLT_PTC_1 at Runtime, while Text2 shows "Motor Overheat", which is the value of FLT_PTC_1 in Resources.resx (which can be translated). The issue is that I can't seem to be able to do what Text2 does using FaultText Property.
Is there a way to do it?
EDIT:
Solved it using mm8 solution, while maintaining the WPF Runtime Localization. The solution isn't pretty at all, since it consists in creating a Binding on a dummy class and then retrieving the binding value as a string, which seems a bit convoluted, but it's the best solution I could find.
public class ResourceConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
string resourceName = value as string;
if (!string.IsNullOrEmpty(resourceName)) //look up the resource here:
{
Binding b = new Binding(resourceName); //Create Binding using as Path the value of FaultText
b.Source = CultureResources.ResourceProvider; //Get the resources from WPF Runtime Localization ObjectDataProvider
BindingOperations.SetBinding(_dummy, Dummy.ValueProperty, b); //Set the Binding to the dummy class instance
return _dummy.GetValue(Dummy.ValueProperty); //Retrieve the value of the Binding from the dummy class instance and return it
}
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
//Initialize Dummy class
private static readonly Dummy _dummy = new Dummy();
//Create a dummy class that accepts the Binding
private class Dummy : DependencyObject
{
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(object), typeof(Dummy), new UIPropertyMetadata(null));
}
}
XAML same as mm8 proposed.
You could bind to the FaultText property and use a converter to look up the resource. Something like this:
public class ResourceConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
string resourceName = value as string;
if (!string.IsNullOrEmpty(resourceName)) //look up the resource here:
return Resource1.ResourceManager.GetString(resourceName);
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
XAML:
<TextBlock Name="Text2">
<TextBlock.Text>
<Binding Path="FaultText">
<Binding.Converter>
<local:ResourceConverter />
</Binding.Converter>
</Binding>
</TextBlock.Text>
</TextBlock>

DisplayMember ComboBox For Which Contains List<Object> as item source

I have Comobox likes this
<dxe:ComboBoxEdit AutoComplete="True" IsTextEditable="False" ImmediatePopup="True" IncrementalFiltering="True" ItemsSource="{Binding Example}" />
In Vm
public List<object> Example
{
get { return example; }
set { example = value; OnPropertyChanged(new PropertyChangedEventArgs("Example")); }
}
public List<ArticlesStock> ArticlesStockList
{
get { return articlesStockList; }
set
{
articlesStockList = value;
OnPropertyChanged(new PropertyChangedEventArgs("ArticlesStockList"));
}
}
Example.Add(ArticlesStockList);
In ArticlesStock class, I have Prop Name Producer a string
How can I set this as my path in ComboBox?
normally we can just set this with props. but here I have a list .and inside I have one more list. in this list the first item value must be set.
C# Conversion how can I set this as Display member
((List<ArticlesStock>)Example[0])[0].WarehouseDeliveryNoteItem.Producer;
I would do the following: define a DataTemplate for your combobox's items and use a converter to retrieve the property you need.
DataTemplate definition:
<ComboBox ItemsSource="{Binding Example}">
<ComboBox.ItemTemplate>
<DataTemplate DataType="{x:Type List}">
<!--no Path is specified, which is equivalent to Path="."-->
<TextBlock Text="{Binding Converter={StaticResource MyConv}}"></TextBlock>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
And the converter used to access the Producer property:
public class MyConv : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
// here value will be an item of Example list, so a List<ArticlesStock>
var val = value as List<ArticlesStock>;
return val[0].Producer;
}
}
Note that I simplified your model structure for brevity.

SelectedItem binding on ComboBox not showing selected value

I'm trying to build a settings page to allow the user to choice which action to execute on item swipe, like the Outlook app.
To do this I created an enum containing the available actions, and I'm binding it to a ComboBox.
Everything works, the user can choose the action and his choice is saved correctly. The problem is that the ComboBox doesn't show the selected item when I navigate to the page, it shows it only after selection.
This means that if user changes selection then the ComboBox is updated, but the selected item is shown as blank upon navigation.
Here's my code:
(XAML)
<ComboBox x:Uid="LeftActionComboBox"
Grid.Row="0"
HorizontalAlignment="Stretch"
SelectedItem="{Binding LeftSwipeActionType, Mode=TwoWay, Converter={StaticResource StringToSwipeActionTypesConverter}}"
ItemsSource="{Binding LeftSwipeActionType, Converter={StaticResource EnumToStringListConverter}}"/>
(VM Property)
public SwipeActionTypes LeftSwipeActionType
{
get { return _settings.LeftSwipeActionTypeProperty; }
set
{
_settings.LeftSwipeActionTypeProperty = value;
// RaisePropertyChanged causes a StackOverflow, but not using it is not the problem since the ComboBox is empty only before set
}
}
(Converter StringToSwipeActionTypesConverter, localization-ready)
// Returns localized string value for the Enum
public object Convert(object value, Type targetType, object parameter, string language)
{
var enumValue = (SwipeActionTypes) value;
switch (enumValue)
{
case SwipeActionTypes.Copy:
return App.ResourceLoader.GetString("CopySwipeActionName");
case SwipeActionTypes.Delete:
return App.ResourceLoader.GetString("DeleteSwipeActionName");
default:
throw new ArgumentOutOfRangeException();
}
}
// Parses the localized string into the enum value
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
var stringValue = (string) value;
if (stringValue.Equals(App.ResourceLoader.GetString("CopySwipeActionName")))
{
return SwipeActionTypes.Copy;
}
if (stringValue.Equals(App.ResourceLoader.GetString("DeleteSwipeActionName")))
{
return SwipeActionTypes.Delete;
}
return null;
}
(Converter EnumToStringListConverter)
public object Convert(object value, Type targetType, object parameter, string language)
{
var valueType = value.GetType();
return Enum.GetNames(valueType).ToList();
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
return value;
}
Any idea on why this is failing?
The reason you are getting a StackOverflow exception is because every time you change LeftSwipeActionType property you are changing the ItemsSource of the ComboBox which changes the SelectedItem which fires INotifyPropertyChanged which changes the ItemsSource and so on and so on.
Once you stop using the same property for ItemsSource and SelectedItem then the correct initial selection will be set.
Rather than use a converter to create your ItemsSource you should just create is in your ViewModel
public MyViewModel(type enumType)
{
SourceForItems = Enum.GetValues(enumType);
}
public IEnumerable SourceForItems { get; private set; }
First of all, here is whats wrong with your approach:
You are binding your ItemsSource to the same property as the SelectedItem, even tough you are using a converter this can cause an infinite update circle - and you don't want that.
Generating the same static list of elements over and over again seems a bit wasteful. Instead of passing an instance of a type, lets just pass the type itself to the converter:
EnumToMembersConverter.cs
public class EnumToMembersConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
return Enum.GetValues((Type)value).ToList();
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
return DependencyProperty.UnsetValue;
}
}
XAML
ItemsSource="{Binding Source={x:Type whateverNamespace:SwipeActionTypes}, Converter={StaticResource EnumToMembersConverter}}"
This will give you all Values of SwipeActionTypes, therefore you can bind it directly, without converting back again.
SelectedItem="{Binding LeftSwipeActionType, Mode=TwoWay}"
There is nothing wrong with using a ComboBox for types other than string, so lets make this your base for further steps:
<ComboBox x:Uid="LeftActionComboBox"
Grid.Row="0"
HorizontalAlignment="Stretch"
SelectedItem="{Binding LeftSwipeActionType, Mode=TwoWay}"
ItemsSource="{Binding Source={x:Type whateverNamespace:SwipeActionTypes}, Converter={StaticResource EnumToMembersConverter}}"/>
The reason you wrote all those converts is probably because the ComboBox showed strange values instead of readable strings. No worries, we already have your converter, you just need to invert it (Convert SwipeActionTypes to String) and apply it to a TextBox:
<ComboBox x:Uid="LeftActionComboBox"
Grid.Row="0"
HorizontalAlignment="Stretch"
SelectedItem="{Binding LeftSwipeActionType, Mode=TwoWay}"
ItemsSource="{Binding Source={x:Type whateverNamespace:SwipeActionTypes}, Converter={StaticResource EnumToMembersConverter}}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=., Converter = {StaticResource SwipeActionTypesStringConverter}}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Note, I didn't run this code so you might need to adjust the used namespaces accordingly

My c# ComboBox does not call the getter after selecting an item. Why?

I have an Combobox with SelectedItem. If I select an item my setter does some calculation and perhaps I want to reset the value to the old one. Unfortunatly my view does no refresh.
I have the following ComboBox :
<ComboBox BorderThickness="0" VerticalAlignment="Center" Margin="2,0"
DisplayMemberPath="Name"
ItemsSource="{Binding ItemsVS.View}"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}" >
</ComboBox>
Here are the properties of my ViewModel:
private CollectionViewSource _itemsVS;
public CollectionViewSource ItemsVS
{
get { return _itemsVS; }
set
{
_itemsVS = value;
if(PropertyChanged!=null)
PropertyChanged(this, new PropertyChangedEventArgs("ItemsVS"));
}
}
private ItemViewModel _selectedItem;
public ItemViewModel SelectedItem
{
get
{
// after setting the old value in the setter the getter is not called
// and the view is not refreshed
return _selectedItem;
}
set
{
var old = _selectedItem;
_selectedItem = value;
// After some logic I want to have the old value!!!!!!!!!!!!!!
_selectedItem = old;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("SelectedItem"));
}
}
If you want to make the ComboBox read back the current value after setting the new value, you need to add a "no op" Converter to your Binding that effectively does nothing. It's a useful little trick, as a binding will not normally check to see whether the source value actually applied matches the new value provided by the binding. Adding a converter forces it to check.
public sealed class NoOpConverter : IValueConverter
{
public static readonly NoOpConverter Instance = new NoOpConverter();
public object Convert(
object value,
Type targetType,
object parameter,
CultureInfo culture)
{
return value;
}
public object ConvertBack(
object value,
Type targetType,
object parameter,
CultureInfo culture)
{
return value;
}
}
<ComboBox SelectedItem="{Binding Path=SelectedItem,
Mode=TwoWay,
Converter={x:Static NoOpConverter.Instance}}"
... />

Where to set the Converter for items of a collection in XAML

I just made my first converter to convert from int to string. I have a combobox fill with integers(years) but if the value is 0 I want the combobox to show 'All'.
This is my converter:
public class IntToString : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value != null)
{
int intY = (int)value;
if (intY == 0)
{
String strY = "All";
return strY;
}
else
{
return intY.ToString();
}
}
return String.Empty;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
}
}
In XAML where should I set the converter ? I tried in the ItemsSource of the combobox:
ItemsSource="{Binding YearsCollection, Converter={StaticResource intToStringYearConverter}}"
But I always get InvalidcastException on this line:
int intY = (int)value;
The problem is that you are trying to convert the entire collection rather than just one item from the collection.
You would want to do something like this:
<ListBox ItemsSource="{Binding YearsCollection}">
<ListBox.ItemTemplate>
<DataTemplate>
<Border DataContext="{Binding Converter={StaticResource intToStringYearConverter}">
...
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
You can't use the converter like this, converter in ItemsSource is supposed to convert whole collection, not individual items. The collection object can't be cast to integer, so you get the exception.
You have to use DataTemplate and apply the converter on individual items.
Or - if all you need is cast to int - you could use ItemStringFormat.
Also, for setting the default message when the source is null, you can use TargetNullValue property of a Binding.

Categories

Resources