1

My target is to create a combobox which displays any list of strings when opened (so the standard behavior), but when the user selects one of the strings, it gets added at the top of the list under a "Recently used" separator.

Essentially, I want a control that behaves exactly like the one to select fonts in MS Word: example from MS Word

My start was to create a custom control with an additional Dependency property which holds the recently selected items. This list gets updated when the user selects an item from the list. I don't want to modify the original list of items, since I aim to get a reusable control where the user does not have to manage the most recent items themselves.

    private static readonly DependencyPropertyKey LastSelectedItemsPropertyKey = 
        DependencyProperty.RegisterReadOnly(
            "LastSelectedItems", 
            typeof (Dictionary<string, int>), 
            typeof (MemoryCombobox),
            new FrameworkPropertyMetadata(default(ObservableCollection<string>), FrameworkPropertyMetadataOptions.None));

    public static readonly DependencyProperty LastSelectedItemsProperty = LastSelectedItemsPropertyKey.DependencyProperty;

My question now is: how can I display all items (labels and both lists) in a single dropdown of the combobox, like this:

---------------------
Label: Recently Selected
---------------------
<All items from the 'LastSelectedItems' DependencyProperty>
---------------------
Label: All Items
---------------------
<All items from the 'ItemsSource' property of the combobox
---------------------

I don't want to use grouping for this, since the items would not be repeated in the "all items" list below the recently used ones, like the user would expect them to be.

Bexo
  • 198
  • 1
  • 7
  • 18
  • Possible duplicate of [Grouping items in a ComboBox](https://stackoverflow.com/questions/3585017/grouping-items-in-a-combobox) – ASh Nov 01 '18 at 08:48
  • @ASh: I edited the question on why I don't think this is good solution for me. This has also been mentioned in the comments of [this question](https://stackoverflow.com/questions/3626797/how-to-display-most-recently-used-list-in-combo-box) – Bexo Nov 01 '18 at 08:56
  • there is always an option to make custom controlTemplate for ComboBox and add more elements in DropDown: Header, list of MRU, Header All, list of all – ASh Nov 01 '18 at 09:02

1 Answers1

1

Have you tried something along these lines. It uses grouping, but does it in a special way, so that mru-items are not removed from the total list/group:

XAML:

<ComboBox Name="MyCombo" SelectionChanged="MyCombo_SelectionChanged" VerticalAlignment="Top">
  <ComboBox.GroupStyle>
    <GroupStyle>
      <GroupStyle.HeaderTemplate>
        <DataTemplate>
          <TextBlock Text="{Binding Name}" Background="DarkGray" Foreground="White" FontWeight="Bold" />
        </DataTemplate>
      </GroupStyle.HeaderTemplate>
    </GroupStyle>
  </ComboBox.GroupStyle>
  <ComboBox.ItemTemplate>
    <DataTemplate>
      <StackPanel Orientation="Horizontal">
        <TextBlock Text="{Binding Name}" Margin="0,0,5,0" />
        <TextBlock Text="{Binding Value}" />
      </StackPanel>
    </DataTemplate>
  </ComboBox.ItemTemplate>
</ComboBox>

Code Behind:

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

      m_preventFeedback = true;
      ItemsList = new ObservableCollection<VMItem>
      {
        new VMItem(new Item("John", 1234), 2),
        new VMItem(new Item("Peter", 2345), 2),
        new VMItem(new Item("Michael", 3456), 2),
      };

      ListCollectionView view = new ListCollectionView(ItemsList);
      view.GroupDescriptions.Add(new PropertyGroupDescription("CategoryId", new ItemGroupValueConverter()));
      MyCombo.ItemsSource = view;
      m_preventFeedback = false;
    }

    private ObservableCollection<VMItem> ItemsList = new ObservableCollection<VMItem>();

    bool m_preventFeedback = false;

    private void MyCombo_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
      if (m_preventFeedback) return;

      if (MyCombo.SelectedItem is VMItem item)
      {
        m_preventFeedback = true;

        VMItem mru = ItemsList.FirstOrDefault(i => i.Name == item.Name && i.CategoryId == 1) ?? new VMItem(item.Item, 1);

        ItemsList.Remove(mru);
        ItemsList.Insert(0, mru);
        MyCombo.SelectedItem = mru;

        m_preventFeedback = false;
      }
    }
  }

  public class ItemGroupValueConverter : IValueConverter
  {
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
      switch ((int)value)
      {
        case 1: return "Last Used";
        case 2: return "Available Items";
      }

      return "N/A";

    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
      return value;
    }
  }

  public class VMItem : INotifyPropertyChanged
  {
    private Item m_item;

    public VMItem(Item item, int categoryId)
    {
      m_item = item;
      m_categoryId = categoryId;
    }

    public string Name
    {
      get { return m_item.Name; }
      set
      {
        m_item.Name = value;
        OnPropertyChanged("Name");
      }
    }

    public int Value
    {
      get { return m_item.Value; }
      set
      {
        m_item.Value = value;
        OnPropertyChanged("Value");
      }
    }

    private int m_categoryId;
    public int CategoryId
    {
      get { return m_categoryId; }
      set
      {
        m_categoryId = value;
        OnPropertyChanged("CategoryId");
      }
    }


    public Item Item => m_item;

    public event PropertyChangedEventHandler PropertyChanged;
    private void OnPropertyChanged(string property)
    {
      PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
    }

  }

  public class Item
  {
    public Item(string name, int value)
    {
      Name = name;
      Value = value;
    }

    public string Name { get; set; }
    public int Value { get; set; }
  }
  • It's not quite what I was looking for, since it requires a special object with a category property and I was hoping to find a more generic solution. But the result works and it shows the most recent items at the top of the list. Thanks for that! +1 but I'll see if there will be a solution closer to what I imagined before I accept this as the answer. – Bexo Nov 02 '18 at 06:05