In a WinUI 3 desktop app, I have a list of objects, each with a LongName
and an Abbreviation
property (both strings). I'd like to use a ComboBox
to select a specific item. When the ComboBox
dropdown is closed, I'd like the Abbreviation
of the SelectedItem
to display in the ComboBox
but when the dropdown opens, I'd like the list to use the LongName
s.
For example, consider the FooBar
class:
public partial class FooBar : ObservableObject
{
public static readonly FooBar[] FooBars =
{
new("Foo1","Bar1"), new("Foo2","Bar2"), new("Foo3","Bar3")
};
public FooBar(string foo, string bar)
{
Foo = foo;
Bar = bar;
}
[ObservableProperty] private string _foo;
[ObservableProperty] private string _bar;
}
and the ComboBox
:
<Grid x:Name="ContentArea">
<ComboBox x:Name="TheComboBox"
SelectedIndex="{x:Bind ViewModel.SelectedFooBar, Mode=TwoWay}"
ItemsSource="{x:Bind classes:FooBar.FooBars}"/>
</Grid>
I'd like TheComboBox
to show the Foo
property for each FooBar
when TheComboBox.IsDropDownOpen
is true
, and the Bar
property when it's false
.
I've tried setting ItemTemplate
, DisplayMemberPath
, ItemContainerStyle
, ItemTemplateSelector
, various tricks in code-behind, and editing the DefaultComboBoxItemStyle
but none of these seem to work to change the property displayed dynamically. Making the change in code-behind seems to trigger SelectedItemChanged
, probably because the Items
list changes (but I'm not sure). I tried editing the DefaultComboBoxStyle
(paricularly the VisualState
s) but it's not obvious to me where individual items are displayed there.
Does anyone have any ideas for this or tips on how I might go about it, please?
Here is one solution mostly based on XAML and databinding with the x:Bind extension (I've used a UserControl
to be able to put the converter resource somewhere because with WinUI3 you can't put it under the Window
element):
<UserControl
x:Class="MyApp.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:classes="using:MyApp.Models">
<UserControl.Resources>
<classes:VisibilityNegateConverter x:Key="vn" />
</UserControl.Resources>
<ComboBox x:Name="TheComboBox" ItemsSource="{x:Bind classes:FooBar.FooBars}">
<ComboBox.ItemTemplate>
<DataTemplate x:DataType="classes:FooBar">
<StackPanel>
<TextBlock Text="{x:Bind Foo}" Visibility="{x:Bind TheComboBox.IsDropDownOpen}" />
<TextBlock Text="{x:Bind Bar}" Visibility="{x:Bind TheComboBox.IsDropDownOpen, Converter={StaticResource vn}}" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</UserControl>
And the converter for the "reverse" visibility conversion between Boolean
and Visibility
("forward" conversion is now implicit in WinUI3 and UPW for some times)
public class VisibilityNegateConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language) => (bool)value ? Visibility.Collapsed : Visibility.Visible;
public object ConvertBack(object value, Type targetType, object parameter, string language) => throw new NotSupportedException();
}
Note: I've tried to use function binding to avoid the need for a converter, something like this:
<TextBlock Text="{x:Bind Bar}" Visibility="{x:Bind TheComboBox.IsDropDownOpen.Equals(x:False)}" />
But compilation fails miserably (maybe a bug in XAML compiler?)