ComboBox values ​​disappear after selection when objects used to display

I have a combo box where I want to display objects and get enumeration values. When first opened, the drop-down list displays the items as intended, but after choosing a value, it seems to disappear from the list. But if the dropdown is active, I can use the keyboard to move up and down between the other values, so they are on the list, but only invisible.

I created a small test application to show my problem. When launched, the application displays a drop-down list with all the choices (the first two are the type of object, the third is a string):

All choices shown on startup

After highlighting the blue line and reopening the combo box, this line is missing:

Blue line selected and is missing in popup

When a line with the text "Green" is highlighted, this line still shows:

Green line selected and still shown in popup

If I chose the red line, the only thing left on the list is the Green test.

I am using the .NET Framework 3.5.

Any tips or tricks why the elements disappear?


Here is all the code needed after starting an empty project in Visual Studio.

MainWindow.xaml.cs:

using System; using System.Collections.Generic; using System.Diagnostics; namespace Test { public partial class MainWindow { public MainWindow() { InitializeComponent(); } private ColorComboBoxValue _activeColor; public ColorComboBoxValue ActiveColor { get { return _activeColor; } set { _activeColor = value; Debug.WriteLine("ActiveColor: " + _activeColor.Color); } } } public class ColorList : List<ColorComboBoxValue> { } public class ColorComboBoxValue { public Color Color { get; set; } public Object Object { get; set; } } public enum Color { Red, Blue, Green } } 

MainWindow.xaml:

 <Window x:Class="Test.MainWindow" x:Name="window" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:System="clr-namespace:System;assembly=mscorlib" xmlns:local="clr-namespace:Test" Title="ComboBoxTest" Height="100" Width="200"> <Window.Resources> <local:ColorList x:Key="ColorList"> <local:ColorComboBoxValue Color="Red"> <local:ColorComboBoxValue.Object> <Path Data="M0,0 L0,30 60,30 60,0 Z" Fill="Red"/> </local:ColorComboBoxValue.Object> </local:ColorComboBoxValue> <local:ColorComboBoxValue Color="Blue"> <local:ColorComboBoxValue.Object> <Path Data="M0,0 L0,30 60,30 60,0 Z" Fill="Blue"/> </local:ColorComboBoxValue.Object> </local:ColorComboBoxValue> <local:ColorComboBoxValue Color="Green"> <local:ColorComboBoxValue.Object> <System:String>Green</System:String> </local:ColorComboBoxValue.Object> </local:ColorComboBoxValue> </local:ColorList> </Window.Resources> <ComboBox ItemsSource="{Binding Source={StaticResource ColorList}}" SelectedItem="{Binding ActiveColor, ElementName=window}"> <ComboBox.ItemTemplate> <DataTemplate> <ContentPresenter Content="{Binding Path=Object}"/> </DataTemplate> </ComboBox.ItemTemplate> </ComboBox> </Window> 
+4
source share
1 answer

It is pretty simple.

A path is a WPF object, and therefore each WPF object can have only one parent object. When a WPF object has a parent set, it cannot be used in another parent.

What happens is that the DataTemplate is loaded and it shows your items. You select one element with Path, and it is set in the selected ContentPresenter element of your Combobox (it should be shown). This separates the path from your original object, causing your elements to “disappear”. Your objects are still present, but you cannot see them because it no longer has a visible object, since the path has been removed from your original list. In the case of a string, this works because the string is not a WPF object.

Hope this makes it a little easier.

So now for the solution:

If you want to save the green text as text, you can do the following:

Make your ColorList of type Color enum:

 public class ColorList : List<Color> { } 

Throw a few things:

 public partial class Window1 : Window { public Window1() { this.Resources["ColorList"] = new[] { Color.Red, Color.Blue, Color.Green }; InitializeComponent(); } private Color _activeColor; public Color ActiveColor { get { return _activeColor; } set { _activeColor = value; } } } public class ColorList : List<Color> { } public enum Color { Red, Blue, Green } 

And expand your DataTemplate to set a specific data pattern for Red and Blue using Trigger objects in the DataTemplate:

 <Window x:Class="WpfApplication6.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:WpfApplication6="clr-namespace:WpfApplication6" Title="ComboBoxTest" Height="100" Width="200"> <ComboBox ItemsSource="{Binding Source={StaticResource ColorList}}" SelectedItem="{Binding ActiveColor, ElementName=ComboBoxTest}"> <ComboBox.ItemTemplate> <DataTemplate> <ContentControl Content="{Binding}" x:Name="content" /> <DataTemplate.Triggers> <DataTrigger Binding="{Binding}" Value="{x:Static WpfApplication6:Color.Red}"> <Setter TargetName="content" Property="ContentTemplate"> <Setter.Value> <DataTemplate> <Path Data="M0,0 L0,30 60,30 60,0 Z" Fill="Red"/> </DataTemplate> </Setter.Value> </Setter> </DataTrigger> <DataTrigger Binding="{Binding}" Value="{x:Static WpfApplication6:Color.Blue}"> <Setter TargetName="content" Property="ContentTemplate"> <Setter.Value> <DataTemplate> <Path Data="M0,0 L0,30 60,30 60,0 Z" Fill="Blue"/> </DataTemplate> </Setter.Value> </Setter> </DataTrigger> </DataTemplate.Triggers> </DataTemplate> </ComboBox.ItemTemplate> </ComboBox> </Window> 

Clean approach:

If you want all the elements to be colored objects, you will need a converter object to convert the color value of the color to the color you want to show:

 <ComboBox ItemsSource="{Binding Source={StaticResource ColorList}}" SelectedItem="{Binding ActiveColor, ElementName=ComboBoxTest}"> <ComboBox.ItemTemplate> <DataTemplate> <Path Data="M0,0 L0,30 60,30 60,0 Z" Fill="{Binding Converter={StaticResource ColorConverter}}"/> </DataTemplate> </ComboBox.ItemTemplate> </ComboBox> 

And a good converter that you need to add to resources:

 public class ColorConverter : IValueConverter { #region IValueConverter Members public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { switch ((Color)value) { case Color.Red: return Colors.Red; case Color.Blue: return Colors.Blue; case Color.Green: return Colors.Green; default: throw new ArgumentOutOfRangeException("value"); } } public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { throw new NotImplementedException(); } #endregion } 

Significantly cleaner;) I hope this helps. If you have any questions, I will answer them in the comments!

+3
source

Source: https://habr.com/ru/post/1309798/


All Articles