2

Perhaps there is a better way to do this, but here's where I'm at right now. I would like to bind a ContextMenu to an ObservableCollection of some complex type. A property on the complex type will determine the icon that is displayed in the menu item. The only way I knew to do this was to create a ComplexTypeToMenuItem converter and associate that with the binding. However, as soon as I add the converter to the binding the context menu no longer updates when the collection has changed. If I remove the converter and rely on ComplexType.ToString() then the menu items update just fine. However, there are no icons in this case.

This can easily be reproduced with the following:

First the XAML

<Window x:Class="GoddamnContextMenuPosition.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:GoddamnContextMenuPosition" 
        Title="MainWindow" Height="350" Width="525">

  <Window.Resources>
    <local:ComplexTypeToMenuItemConverter x:Key="ComplexTypeToMenuItemConverter" />
  </Window.Resources>

    <Grid>
        <TextBox Grid.Row="0" Text="Hooray">
            <TextBox.ContextMenu>
                <ContextMenu ItemsSource="{Binding Objects, Source={x:Static local:Settings.Instance}, Converter={StaticResource ComplexTypeToMenuItemConverter}}">
                </ContextMenu>
            </TextBox.ContextMenu>
        </TextBox>

        <Button Grid.Row="1" Content="Add" Height="25" Width="50" Click="Button_Click"></Button>
    </Grid>
</Window>

And now the code

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        Settings.Instance.Objects.Add(new ComplexType()
        {
            Text = "NEW", Type = 4
        });
    }
}

public class ComplexType
{
    public ComplexType()
    {
        this.TimeStamp = DateTime.Now;
    }

    public string Text
    {
        get;
        set;
    }

    public int Type
    {
        get;
        set;
    }

    public DateTime TimeStamp
    {
        get;
        set;
    }
}

public class ComplexTypeToMenuItemConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        var values = value as IEnumerable<ComplexType>;

        if (values == null)
        {
            return new List<MenuItem>
            {
                new MenuItem
                {
                    Header = "Unknown Value"
                }
            };
        }

        IList<MenuItem> items = new List<MenuItem>();

        foreach (ComplexType obj in values)
        {
            BitmapImage bitmap = new BitmapImage();
            bitmap.BeginInit();
            bitmap.UriSource = new Uri("1.png", UriKind.Relative);
            bitmap.EndInit();

            Image image = new Image();
            image.Width = 16;
            image.Source = bitmap;

            MenuItem menuItem = new MenuItem();
            menuItem.Header = string.Format("{0} - {1}", obj.Text, obj.TimeStamp.ToShortTimeString());
            menuItem.Icon = image;

            items.Add(menuItem);
        }

        return items;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

public class Settings
{
    private static readonly Settings settings = new Settings();

    private Settings()
    {
        this.Objects = new ObservableCollection<ComplexType>
        {
            new ComplexType
            {
                Text = "Item #1",
                Type = 1
            }
        };
    }

    public static Settings Instance
    {
        get
        {
            return settings;
        }
    }

    public ObservableCollection<ComplexType> Objects
    {
        get;
        private set;
    }
}

Any help will be greatly appreciated!

H.B.
  • 166,899
  • 29
  • 327
  • 400
EricTheRed
  • 289
  • 5
  • 14

1 Answers1

2

You turn the ObservableCollection into a List, which does no longer provide updates, hence the problem. In general you want to avoid converters in ItemsSource bindings.

Do not use converters to do this sort of thing, use Data Templating.

H.B.
  • 166,899
  • 29
  • 327
  • 400
  • 1
    Yup, that will do it. Well I feel dumb. Thanks for the tip on templating too. That works great for the header content, but I'm still not sure how to dynamically set the icon within the template. Triggers do not seem appropriate because the object that I'm binding to already has the correct icon path. I also tried using a style, but then only one item in the menu ever had an icon at any one time. – EricTheRed Jan 21 '12 at 23:25
  • @EricTheRed: See [this](http://stackoverflow.com/questions/7294302/databound-menu-with-icons/7294452#7294452) or [this](http://stackoverflow.com/questions/6177550/wpf-mvvm-menuitems-styling-issue-with-icons), it's a common issue. – H.B. Jan 21 '12 at 23:49