Binding ComboBox Text property using a converter - wpf

I'm having troubles using a WPF ComboBox in the following scenario:
ViewModel
Provides a ObservableCollection<T>; This collection contains a list of items the user may select.
Provides a property of type T representing the selected item.
The user shall be able to select either an existing item from the items in the ObservableCollection<T> or add a new item by typing in the string representation.
I have a converter available able to convert a item of type T to string and vice versa.
View
My ComboBox bound to the collection and the selected item properties:
<ComboBox ItemsSource="{Binding Path=MyObservableCollection}"
SelectedItem="{Binding Path=MySelectedItem}"
IsReadOnly="False" IsEditable="True"/>
The data template used to display the items correctly:
<DataTemplate DataType="{x:Type T}">
<TextBlock Text="{Binding Converter={StaticResource ResourceKey=MyConverter}}"/>
</DataTemplate>
The Problem
Items in the drop down list of the ComboBox are displayed correctly using the convert. The selected item displayed in the TextBox of the ComboBox is not displayed correctly; Instead of using my converter, the ToString method is used.
Is it possible to specify a converter for the Text property? I tried using the following code:
<ComboBox ItemsSource="{Binding Path=MyObservableCollection}"
SelectedItem="{Binding Path=MySelectedItem}"
Text="{Binding Path=MySelectedItem, Converter={StaticResource ResourceKey=MyConverter}}"
IsReadOnly="False" IsEditable="True"/>
This solves the display problem but now I get the Type.FullName of T in the converters ConvertBack method - which can of course not be converted.
Summary
I want the user to be able to select an item from a collection, allowing him to add new items by entering the string representation. The items in the collection shall be converted between string and object representation using a converter. The conversion shall be done in both the drop down list and the text box of the ComboBox.
Edit
Here is the code in my converter - no magic there just straightforward conversion:
public class MyConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return MyConverter.Convert(value as T);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return MyConverter.Convert(value as string);
}
public static string Convert(T key)
{
// Conversion from T to string.
}
public static T Convert(string key)
{
// Conversion from string to T.
}
}
Ok, now I found something that does what I want:
<TextBox Text="{Binding Path=MySelectedItem, Converter={StaticResource ResourceKey=MyConverter}}"/>
<ListBox ItemsSource="{Binding Path=MyObservableCollection}"
SelectedItem="{Binding Path=MySelectedItem, Converter={StaticResource ResourceKey=MyConverter}}"/>
This does exactly what I want; I can select predefined values and the user may add values on his own. Is it possible to do this with a ComboBox?

In case someone facing same issue and don't want to cope with having a string property to bound to.
You can use the following
<ComboBox
ItemsSource="{Binding Path=MyObservableCollection}"
Text="{Binding MySelectedItem, Converter={StaticResource DisplayConverter}}"
SelectedValue="{Binding MySelectedItem, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
>
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={StaticResource DisplayConverter}}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
Notice the binding is done on the SelectedValue, not SelectedItem.
Then the display converter is added both to the Text & Itemtemplate properties.
On the Text property it will be used to convert the selected item display value.
On the ItemTemplate, to convert display values within the list box
I even use this snippet with Enum collection coming from ObjectDataProvider defined in the xaml. My enums have DisplayString attribute and combobox behave just fine to display the string value representation of the enums.
HTH

I now use a different approach for the problem:
My view model provides a observable collection and a string property. The collection is bound to the ItemsSource property of the ComboBox, the selected item to the string property.
<ComboBox
ItemsSource="{Binding Path=MyCollection}"
Text="{Binding Path=MyProperty, Mode=TwoWay, UpdateSourceTrigger=LostFocus}"
IsReadOnly="False" IsEditable="True"/>
UpdateSourceTrigger=LostFocus part is used to prevent unnecessary updates.

Related

How to display a different WPF DataTemplate for multiple object types

The following blog post demonstrates how to dynamically display a different DataTemplate depending on which object is displayed in a ListBox:
http://www.thejoyofcode.com/different_datatemplates_for_different_types.aspx
Although this is useful, my particular situation is a bit more challenging.
I have a collection of objects in my main viewmodel:
public IEnumerableCollection<IGenericObject> CurrentObjects
I currently display them in a ComboBox using XAML as follows:
<ComboBox ItemsSource="{Binding CurrentObjects}"
SelectedItem="{Binding SelectedObject,
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged }"
DisplayMemberPath="Name"
SelectedIndex="0"/>
I would now like to have a separate panel below the ComboBox which displays properties for each object. However, each object has different properties depending on its concrete type.
For example, a AObject would not only support IGenericObject but IAObject as well, so I want to always display properties that are common to all objects and then dynamically display those that are specific whatever object is currently selected.
I don't know how to set up the bindings and obtain the property values for each specific object type given the collection I have makes use of the common IGenericObject interface.
How can I achieve this?
which displays properties for each object.
Keep in mind that binding at the end of the process is just reflection. One can place property names and if the binding fails, nothing is shown.
However, each object has different properties depending on its concrete type.
Ultimately we are using a converter to hide labels and textblocks depending on the target type contained as the selected item in the combobox.
Example
This example is a person and an employee which share the same attributes such as First, Last and Phone, but the employee has an EmployeeId unlike the person. We want to show the first and last name for both, but if its an employee also show its Id. Also swap out the headers stating if it is an employee or a person.
Last names in combo, two objects, Smith is a person and Man is an employee. So when we have a person the screen shows this:
and when its an employee show this with its ID for the last info:
Xaml
<Page x:Class="WPFStack.Views.BindingCoverterPage"
...
xmlns:local="clr-namespace:WPFStack.Views"
xmlns:model="clr-namespace:WPFStack.Model"
xmlns:converter="clr-namespace:WPFStack.Converters"
.>
<Page.Resources>
<model:People x:Key="people">
<model:Person First="Joe"
Last="Smith"
Phone="303-555-5555" />
</model:People>
<model:Employees x:Key="employeePeople">
<model:Employee First="Omega"
Last="Man"
Phone="303-867-5309"
EmployeeId="90125" />
</model:Employees>
<converter:EmployeeVisiblity x:Key="isEmployeeVisibility"/>
<Style x:Key="LabelStyle" TargetType="Label" >
<Setter Property="Margin" Value="-20,0,0,0"/>
<Setter Property="FontWeight" Value="Bold"/>
</Style>
</Page.Resources>
<StackPanel Margin="10"
HorizontalAlignment="Left">
<ComboBox Name="mySelectionCombo"
SelectedItem="{Binding SelectedObject,
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged }"
DisplayMemberPath="Last"
SelectedIndex="0"
Width="200">
<ComboBox.ItemsSource>
<CompositeCollection>
<CollectionContainer Collection="{Binding Source={StaticResource people}}" />
<CollectionContainer Collection="{Binding Source={StaticResource employeePeople}}" />
</CompositeCollection>
</ComboBox.ItemsSource>
</ComboBox>
<StackPanel Margin="10">
<Label Content="Employee Stats:"
Style="{StaticResource LabelStyle}"
Visibility="{Binding SelectedItem,
ElementName=mySelectionCombo,
Converter={StaticResource isEmployeeVisibility}}" />
<Label Content="Person Stats:"
Style="{StaticResource LabelStyle}"
Visibility="{Binding SelectedItem,
ElementName=mySelectionCombo,
ConverterParameter=Reverse,
Converter={StaticResource isEmployeeVisibility}}" />
<TextBlock Text="{Binding SelectedItem.First, ElementName=mySelectionCombo}" />
<TextBlock Text="{Binding SelectedItem.Last, ElementName=mySelectionCombo}" />
<TextBlock Text="{Binding SelectedItem.EmployeeId, ElementName=mySelectionCombo}"
Visibility="{Binding SelectedItem,
ElementName=mySelectionCombo,
Converter={StaticResource isEmployeeVisibility}}" />
</StackPanel>
</StackPanel>
</Page>
Converter
namespace WPFStack.Converters
{
public class EmployeeVisiblity : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var isVisible = Visibility.Collapsed;
if (value != null)
if (value is Employee)
{
if (parameter == null) // Does not say string "Reverse"
isVisible = Visibility.Visible;
}
else // Value is a person
{
if (parameter != null) // Does say string "Reverse"
isVisible = Visibility.Visible;
}
return isVisible;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
}
}
Usage
Note that what the example is primarily doing is simply binding to a similar (but not the same type of things as your data) for it is process and not the data one needs to focus on for the answer to the question.
Even though I am using a Composite Collection to hold set of data such as yours via ItemsSource, I end up (like you) with a list of different instance objects, just like your data.
So....
Focus on that point on and see how the converter works to determine what to make visible and what not depending on what the combobox has selected.
Here are the following steps which you will need to implement and understand to make it work in your code:
Make a converter like the one in your project. Note the namespace.
Make a xaml import reference and name it converter. My namespace was WPFStack.Converters so bringing it into Xaml made mine xmlns:converter="clr-namespace:WPFStack.Converters".
Create a static instance in Xaml of the converter you created by specifying it in the page resources <converter:EmployeeVisiblity x:Key="isEmployeeVisibility"/>.
Any visual control on the screen you need to hide, on its Visbility property will bind to the current selected item of the combo (which you may need to provide a name for the binding) and also call your converter to determine if it is shown or not such as this xaml code:
<TextBlock Text="{Binding SelectedItem.EmployeeId, ElementName=mySelectionCombo}"
Visibility="{Binding SelectedItem,
ElementName=mySelectionCombo,
Converter={StaticResource isEmployeeVisibility}}" />
That is the takeaway you need to have from this response.

Displaying cultures in combo box and selecting

I'm trying to display cultures in the combox box and I want the user to receive the DisplayName of the culture when selected but I get culture code ie 'en', 'ar' etc
The itemsource of the combo box is
ItemsSource="{Binding Path=SupportedCultures, Mode=OneWay}" SelectedItem="{Binding SelectedItem.Language}" SelectedValue="DisplayName" DisplayMemberPath="DisplayName"
SupportedCultures property
public static List<CultureInfo> SupportedCultures
{
get
{
return _SupportedCultures;
}
}
How can I get DisplayName in my selectedItem's Language property which is of type string?
SelectedItem requires the same kind of object that your ItemsSource is bound to, so don't use that. If you want to select by the value of a property, then use SelectedValue, and to tell WPF which property to check for that value, use SelectedValuePath:
ItemsSource="{Binding SupportedCultures, Mode=OneWay}"
SelectedValue="{Binding SelectedItem.Language}"
SelectedValuePath="DisplayName"
DisplayMemberPath="DisplayName"
Your code is almost correct. Your List is static. So, to bind to it, you should use {x:Static} source:
ItemsSource="{Binding Source={x:Static yournamespace:YourClassName}, Path=SupportedCultures, Mode=OneWay}"
DisplayMemberPath="DisplayName"
Note that SelectedItem="{Binding SelectedItem.Language}" binds your selected CultureInfo to your ComboBox's DataContext. So, in this case a DataContext of your ComboBox should have object SelectedItem with CultureInfo Language {get;set;} property. I don't think that's what you are looking for?)

WPF bound combobox using converter

I have a ComboBox that is bound to an EnumerableRowCollection<T> :
ComboFamilyStatus.ItemsSource = EnumerableRowCollection<TaxDataSet.SourcesOfValuesRow> coll;
My xaml lookes like this:
<ComboBox Name="ComboFamilyStatus" DisplayMemberPath="Description"
Text="{Binding FamilyStatus, Converter={StaticResource FamilyStatusStringConverter}}">
I'm using the DisplayMemberPath to show the description of the row. The SourcesOfValuesRow has a value and a description and in the combo I want to see the description text. The Text is bound to the database where the FamilyStatus is saved as an int value this is why I added a converter.
My question is if the converter could convert from the int value to the string using the itemsource from the combobox? I don't see that the converter knows anything about the combo. In the meantime I wrote the converter to take again the EnumerableRowCollection<TaxDataSet.SourcesOfValuesRow> from the database and find there the matched description - this can't be the simplest way to do this!
Any suggestions??
In this case, you're better off using a DataTemplate, instead of a Converter.
You already have a data class. Just use a DataTemplate that inserts a Textblock bound to the int value, then apply your converter there.
<ComboBox>
<ComboBox.ItemTemplate>
<DataTemplate DataType="{x:Type local:TaxDataSet.SourcesOfValuesRow}">
<TextBlock Text="{Binding FamilyStatus, Converter={StaticResource FamilyStatusStringConverter}}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
<ComboBox>
Change your SourcesOfValuesRow FamilyStatusProperty to an enum. Deriving from int lets you cast it directly.
enum FamilyStatusValues : int
{
[Description("Married")]
Married,
[Description("Divorced")]
Divorced,
[Description("Living Together")]
LivingTogether
}
Then in your converter use this code
ConvertTo(object value, ...)
{
FieldInfo field = value.GetType().GetField(value.ToString());
object[] attribs = field.GetCustomAttributes(typeof(DescriptionAttribute), true));
if(attribs.Length > 0)
{
return ((DescriptionAttribute)attribs[0]).Description;
}
return string.Empty;
}
There is no need to use any converter. It worked for me using this:
<ComboBox Name="FamilyStatus" Grid.Row="7" Grid.Column="1" ItemsSource="{Binding Source={StaticResource comboProvider}}"
SelectedValuePath="Value" DisplayMemberPath="Description" SelectedValue="{Binding FamilyStatus}">
Where DisplayMemberPath is the string from the TaxDataSet.SourcesOfValuesRow and SelectedValuePath is the int value. The SelectedValue is the value from the contact table (instead of writing in the combo Text="{Binding FamilyStatus, Converter={StaticResource FamilyStatusStringConverter}}).

Displaying Items from Two Collections in One ListBox using WPF Datatemplates

I'm trying to create a Listbox that displays text and an image as one item. The listbox items have the text and the ID of the image. The image ID can be used in a LINQ expression to get the container of the image URI.
basically, I have two lists. Each item in the first list is a key to retrieve a specific item in the second list. How can I get both pieces of data to display in one listbox?
EDIT FOR CLARITY:
the first list is a collection of message objects - ObservableCollection:
class Message
{
public String PosterID { get; set; }
public String Text { get; set; } //this isn't important, it is the message body
}
The second list is a collection of user profiles - ObservableCollection:
class User
{
public String UserID { get; set; }
public String ProfilePictureURI { get; set; }
}
To get the profile data of the poster of a message, you must take 'Message.PosterID' and use that as a 'key' to match 'User.UserID'.
The listbox in question is currently databound to ViewModel.Messages. In the listbox's data template, I print out 'Message.Text' successfuly, BUT I still need to retrieve 'User.ProfilePictureURI.'
I am being recommended to use a ValueConverter to convert 'Message.PosterID' to 'User.UserID.' But to make this conversion, I need to pass ViewModel.Users into the ValueConverter. I currently don't know how to do this.
I think you have two ways:
A. Use a binding converter to convert the image id by looking up your image id from the second list, then bind your ListBox to the list.
B. Create a wrapper class that acts as a ViewModel for your data, and bind the ListBox to the ViewModel object.
EDIT
About using the value converter:
If you could make the Users list static or use some Dependency Injection mechanism to obtain the Users list, then your value converter could easily do the conversion.
The only other way is somehow pass the Users list from ViewModel (DataContext) to the binding converter.
At first I thought that you can set the Users to a property of the converter, like this:
<ListBox ItemsSource="{Binding Path=Messages}">
<ListBox.Resources>
<c:PosterIDConverter x:Key="pidConverter" Users="..."/>
</ListBox.Resources>
...
Or pass it as ConverterParameter to the binding:
<TextBlock Text="{Binding Path=Text, Converter={StaticResource pidConverter,ResourceKey}, ConverterParameter=...}"/>
But how should I get the Users property from the DataContext? In any of the two above options, you should be able to bind the property to Users, like this (incorrect):
<c:PosterIDConverter x:Key="pidConverter" Users="{Binding Path=Users"/>
But PosterIDConverter is not DependencyObject and does not support data binding.
Working Solution:
Here is what I finally came to.
Your converter class:
public class PosterIDConverter : IMultiValueConverter
{
public object Convert(object[] value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
string userId = (string)value[0];
IEnumerable<User> users= (IEnumerable<User>)value[1];
var user = users.FirstOrDefault(u => u.UserID == userId);
if (user != null)
return user.ProfilePictureURI;
return null;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Your xaml file (just a sample):
<ListBox x:Name="lst" DataContext="{StaticResource vm}" ItemsSource="{Binding Path=Messages}">
<ListBox.Resources>
<c:PosterIDConverter x:Key="pidConverter"/>
</ListBox.Resources>
<ListBox.ItemTemplate>
<DataTemplate DataType="{x:Type c:Message}">
<Border BorderBrush="Blue" BorderThickness="1">
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Message: "/>
<TextBlock Text="{Binding Path=Text}"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="URI: "/>
<TextBlock>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource pidConverter}">
<Binding Path="PosterID"/>
<Binding ElementName="lst" Path="DataContext.Users"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</StackPanel>
</StackPanel>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
In this sample I bound the Text property of a TextBlock to the PosterID and Users property at the same time and used the value converter (IMultiValueConverter) to convert them to image URI.

WPF Combobox is changing source via SelectedValue when ItemsSource is loaded

I have this combobox in my WPF window.
<ComboBox DisplayMemberPath="Description" SelectedValuePath="ID" ItemsSource="{Binding Source={StaticResource CvsPrinters}}" SelectedValue="{Binding CheckPrinterID}" />
My problem is that when loading the window, the SelectedValue binding is causing my source data to change to the first item in the ItemsSource, instead of setting the Combobox's SelectedValue to the appropriate item in the ItemsSource.
The CheckPrinterID is from a listview selection datacontext, and this problem only occurs to the item initially selected in that listview on load. When I select another item in the listbox, the combobox correctly selects the proper item and all is fine, but unfortunately my initial item has been updated and is now incorrect.
I guess you are trying to synchronize ListView and ComboBox through a common property. Try setting IsSynchronizedWithCurrentItem to True in ListView and make sure SelectedItem or SelectedIndex for ListView is set during load.
Try re-arranging ItemsSource before DisplayMemberPath.
If you have some flexibility in the DataContext object you could try changing the selected CheckPrinter property to be of the data object type instead of the ID and switch to using SelectedItem instead of SelectedValue (for some reason SelectedValue behaves differently, especially at initial load) and then extract the ID from that value in code.
If you can't use the CheckPrinter objects in your DataContext object for whatever reason, you could also go the opposite direction on the UI side by using a list of IDs as the ItemsSource, and again using SelectedItem. To get the list to show what you want in the ComboBoxItems you would then need to use an IValueConverter to pull out Description values based on IDs:
<ComboBox ItemsSource="{Binding Source={StaticResource CvsPrinterIds}}" SelectedItem="{Binding CheckPrinterID}" >
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock >
<TextBlock.Text>
<Binding>
<Binding.Converter>
<local:MyDescriptionLookupConverter Printers="{StaticResource CvsPrinters}"/>
</Binding.Converter>
</Binding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
and a simple converter to do the ID-Description lookup (add some null and cast checks):
public class MyDescriptionLookupConverter : IValueConverter
{
public IEnumerable<Printer> Printers { get; set; }
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return Printers.First(p => p.Id == (int)value).Description;
}
...
}

Resources