views:

194

answers:

1

I have a combobox where I want to display objects and have enum values returned. When first opened the combobox displays the items as supposed, but after a value is chosen it seems to disappear from the list. But if the combobox is active I can use the keyboard to navigate up and down between the other values, so they are in the list but only invisible.

I have created a little test application to show my problem. When started the application shows the combobox with all the choices (the two first are type of Object, the third is a String):

All choices shown on startup

After the blue line is selected and when the combobox is opened again this line is missing:

Blue line selected and is missing in popup

When the line with the text "Green" is selected that line is still showing:

Green line selected and still shown in popup

If I had chosen the red line the only thing that would still be in the list is the test "Green".

I am using .NET Framework 3.5.

Any hints or tips to why the elements disappears?


Here are all the code needed after starting a blank 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>
+1  A: 

Its quite simple really.

Path is a WPF object, and as such, each WPF object can only have 1 parent object. Once a WPF object has a Parent set, it cannot be used in another parent.

What happens is this, the DataTemplate gets loaded, and it shows your items. You select one item with a Path, and it gets set in the Selected Item ContentPresenter of your Combobox (it has to be shown). This detaches the Path from your original object, resulting in your items 'disappearing'. Your items are still present, but you cannot see them because it has no visible object anymore as the Path has been detached from your original list. In case of string it works because string is no WPF object.

Hope this clears things up a bit.

So, now for a solution:

If you wish to keep the Green as text, you can do the following:

Make your ColorList of the type Color enum:

public class ColorList : List<Color> { }

Throw some stuff away:

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 datatemplate for Red and Blue using Trigger objects in your 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>

The clean approach:

If you wish to let all items be a color object, you will need a converter object to convert a Color enum value into the Color you wish 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 nice converter which you need to add to the 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
}

Much cleaner ;) Hope this helps.. if you have any questions, I will answer them in the comments!

Arcturus
Thanks a lot for your answer, the solution worked great! The part with binding to get color wasn't necessary for me because all the objects in my application is different paths in the same color. Just made an small example application with colors to easily demonstrate my problem. Thanks again :-)
Gakk
Also since the resource is created as an array i didn't need to create a new class "public class ColorList : List<Color> { }"
Gakk
No problem mate! Glad I could help!
Arcturus
And with this XAML no code-behind was needed: <UserControl.Resources> <x:Array x:Key="ColorList" Type="{x:Type local:Color}"> <x:Static Member="local:Color.Red"/> <x:Static Member="local:Color.Blue"/> <x:Static Member="local:Color.Green"/> </x:Array> </UserControl.Resources>. The formating isn't shown in the comment, but anyone interested will see it anyway. Thanks again to Arcturus :-)
Gakk