SelectedItem binding on ComboBox not showing selected value - c#

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

Related

Caliburn.Micro - Binding ObservableCollection of ValueTuple to ComboBox

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>

File path to file name String converter not working

Using a wpf ListBox I'm trying to display a list of filename without displaying the full path (more convenient for user).
Data comes from an ObservableCollection which is filled using Dialog.
private ObservableCollection<string> _VidFileDisplay = new ObservableCollection<string>(new[] {""});
public ObservableCollection<string> VidFileDisplay
{
get { return _VidFileDisplay; }
set { _VidFileDisplay = value; }
}
In the end I want to select some items and get back the full file path. For this I have a converter :
public class PathToFilenameConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
//return Path.GetFileName(value.ToString());
string result = null;
if (value != null)
{
var path = value.ToString();
if (string.IsNullOrWhiteSpace(path) == false)
result = Path.GetFileName(path);
}
return result;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
}
Which I bind to my listbox itemsource :
<ListBox x:Name="VideoFileList" Margin="0" Grid.Row="1" Grid.RowSpan="5" Template="{DynamicResource BaseListBoxControlStyle}" ItemContainerStyle="{DynamicResource BaseListBoxItemStyle}" ScrollViewer.HorizontalScrollBarVisibility="Disabled" ItemsSource="{Binding Path=DataContext.VidFileDisplay, Converter={StaticResource PathToFileName},ElementName=Ch_Parameters, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" SelectedItem="{Binding Path=SelectedVidNames,ElementName=Ch_Parameters, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}">
Without the converter, it is working fine (but of course this is the full path displayed in the listbox). With the converter I have one character per line... displaying this :
System.Collections.ObjectModel.ObservableCollection`1[System.String]
Where am I wrong ?
Thank you
In ItemsSource binding converter applies to the whole list and not to each item in the collection. If you want to apply your converter per item you need to do it ItemTemplate
<ListBox x:Name="VideoFileList" ItemsSource="{Binding Path=DataContext.VidFileDisplay, ElementName=Ch_Parameters}" ...>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=., Converter={StaticResource PathToFileName}}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

WPF ComboBox initial state from a string property

I have a simple combo :
<ComboBox x:Name="testCombo" SelectedValue="{Binding State, Mode=TwoWay}" VerticalAlignment="Center" HorizontalAlignment="Center" MinWidth="100">
<ComboBoxItem>OPEN</ComboBoxItem>
<ComboBoxItem>CLOSED</ComboBoxItem>
</ComboBox>
That state is just a string property with INotifyPropertyChanged implemented.
private string state;
public string State
{
get { return state; }
set
{
state = value;
OnPropertyChanged("State");
}
}
What i want to achieve is, when that State string property is initially set to "OPEN", when my window loads, the ComboBox to set it's initial item as "OPEN".
I also tried to attach a converter there:
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
ComboBoxItem cbi = new ComboBoxItem();
cbi.Content = value as string;
return cbi;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return (value as ComboBoxItem).Content;
}
With this, my State string property will be fine populated, but the ComboBox won't get to the desired initial state.
ComboBoxItem is not compared by content but by reference and the instance you create in converter is not the same instance displayed in ComboBox so they will never be equal. What you can do is set ItemsSource as list of strings and bind SelectedItem directly to string property without any converter
<ComboBox SelectedItem="{Binding State, Mode=TwoWay}" x:Name="testCombo">
<ComboBox.ItemsSource>
<x:Array Type="{x:Type sys:String}">
<sys:String>OPEN</sys:String>
<sys:String>CLOSED</sys:String>
</x:Array>
</ComboBox.ItemsSource>
</ComboBox>
you'll need to add sys namespace to your XAML as well
xmlns:sys="clr-namespace:System;assembly=mscorlib"

How to data-bind to a property of a composite object in a collection?

I need to allow selection of several items from this predefined list:
public enum QuarkType{
Up,
Down,
[Description("Magical Being")] Charm,
[Description("Quite Odd")] Strange,
Top,
Bottom
}
So I use CheckComboBox, and use the DescriptionAttribute where I need to use custom description. I feed the CheckComboBox using a MarkupExtension that returns a list of all values of the given enum as IEnumerable<EnumDescriptionPair>, where EnumDescriptionPair is:
public class EnumDescriptionPair{
public object Value { get; set; }
public string Description { get; set; }
}
Now the problem is how to pass the Values of this list to the code-behind list:
public ObservableCollection<QuarkType> SelectedQuarksList { get; set; }
I mean, how to take just the Value out of the EnumDescriptionPair for each item of the selected list ?
This is what I have thus far. It obviously doesn't work (meaning it shows the right strings in the CheckComboBox, and allows selecting several items, but isn't reflected in the SelectedQuarksList mentioned above):
<Window x:Class="MyEditor.MainWindow"
xmlns:loc="clr-namespace:MyEditor"
xmlns:toolKit="clr-namespace:Xceed.Wpf.Toolkit;assembly=Xceed.Wpf.Toolkit"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<StackPanel>
<toolKit:CheckComboBox x:Name="Ccb" Delimiter=","
ItemsSource="{loc:EnumItemsValueConverter {x:Type loc:QuarkType}}"
DisplayMemberPath="Description"
SelectedItemsOverride="{Binding SelectedQuarksList}" />
<ListBox ItemsSource="{Binding SelectedQuarksList}" />
</StackPanel>
</Window>
To do exactly what your question asks, you could try using a converter on the SelectedQuarksList binding that does a ".Select(q => q.Value)" in the ConvertBack function.
To get the behavior you want, I have done this successfully in the past (example with 2 of your values), this sets up the enum as "Flags" so the value sequence goes 0, 1, 2, 4...:
<StackPanel Orientation="Horizontal">
<Checkbox Content="Up" IsChecked="{Binding Path=SelectedQuarksFlags, Converter={Static Resource HasFlagToBoolConverter}, ConverterParamater={x:Static Quarks.Up}}"
<Checkbox Content="Magical Being" IsChecked="{Binding Path=SelectedQuarksFlags, Converter={Static Resource HasFlagToBoolConverter}, ConverterParamater={x:Static Quarks.Charm}}"
</StackPanel>
The converter looks like:
Quark _lastSeenValue;
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
Quark paramQuark = (Quark)parameter;
Quark currentQuark = (Quark)value;
_lastSeenValue = currentQuark;
return currentQuark.HasFlag(paramQuark);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
Quark newQuark = _lastSeenValue;
Quark paramQuark = (Quark)parameter;
if ((bool)value)
{
newQuark |= paramQuark;
}
else
{
newQuark &= ~paramQuark;
}
_lastSeenValue = newQuark;
return newQuark;
}
This could be converted to add or remove from a list relatively easily, but I know the code above works.

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