2

I have a ComboBox that displays strings. How can I add an option to remove some items from the ComboBox list? I tried:

<ComboBox.ContextMenu>
    <ContextMenu>
        <MenuItem Header="Remove" Click="MenuItem_OnClick"></MenuItem>
    </ContextMenu>
</ComboBox.ContextMenu>

But I don't know how to locate the item the user chose:

private void MenuItem_OnClick(object sender, RoutedEventArgs e) {
    /* ... ??? ... */
}

I don't mind putting some icon next to each item, that removes its related item when clicked, but don't know how to do it..

Summary:

This is how I solved it, finally (The credit belongs to Nawed Nabi Zada, who provided the main idea of "climbing" using the VisualTreeHelper.GetParent(...) to get the ComboBoxItem, in the accepted answer, below)

<ComboBox IsEditable="True" Name="RemotePathComboBox" VerticalAlignment="Center"
          SelectionChanged="RemotePathComboBoxOnSelectionChanged"
          Grid.Column="1" Margin="0,6" KeyUp="HostNameOrIPAddress_OnKeyUp">
    <ComboBox.ItemTemplate>
        <DataTemplate>
            <DockPanel>
                <Button Click="RemoveRemotePathItem_Click" Margin="5" DockPanel.Dock="Left">
                    <Image Source="{Binding Converter={StaticResource iconExtractor}, ConverterParameter=%WinDir%\\System32\\shell32.dll|131}"/>
                </Button>
                <TextBlock Name="ItemTextBlock" VerticalAlignment="Center" Text="{Binding Path=Path}"></TextBlock>
            </DockPanel>
        </DataTemplate>
    </ComboBox.ItemTemplate>
</ComboBox>

Code-behind:

private void RemoveRemotePathItem_Click(object sender, RoutedEventArgs e) {
    var depObj = sender as DependencyObject;

    while (!(depObj is ComboBoxItem)) {
        if (depObj == null) return;
        depObj = VisualTreeHelper.GetParent(depObj);
    }

    var comboBoxItem = depObj as ComboBoxItem;
    var item = comboBoxItem.Content as RemotePathItem;

    _remotePathsList.Remove(item);
    RemotePathComboBox_SelectIndexWithoutChangingList(0);
}

(The "Icon Extractor" that fetches the icon from the system's DLL is from an old post of mine)

Community
  • 1
  • 1
Tar
  • 8,529
  • 9
  • 56
  • 127
  • Though it diverts from your current strategy, you might consider just handling the `DEL` key. – Mike Perrenoud Jan 13 '14 at 14:23
  • @MichaelPerrenoud, it wouldn't be very user-friendly. – Tar Jan 13 '14 at 14:26
  • Use [SelectedItem](http://msdn.microsoft.com/en-us/library/system.windows.controls.primitives.selector.selecteditem%28v=vs.110%29.aspx)? ContextMenu on a ComboBox seems wrong to me though... – UIlrvnd Jan 13 '14 at 14:29
  • @StefanDenchev, yes, I can select the item first, then do something (right-click/click on some "remove" button/etc...), but I'm looking for the intuitive way for the user - so he'd be able to remove the items he wants one by one, not having to select-remove - select-remove - select-remove - ... – Tar Jan 13 '14 at 14:36
  • You mean right-clicking doesn't select the item (like i said, i'd never put a context menu on a combo)? – UIlrvnd Jan 13 '14 at 14:38
  • Look, what you're doing just doesn't seem right in wpf, you can *probably* use hittesting, but... i wouldn't. – UIlrvnd Jan 13 '14 at 14:52

3 Answers3

2

You can also do it this way:

<Window x:Class="RemoveItemsFromComboBox.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="350" Width="525">
<Grid>
    <ComboBox x:Name="CbxItems" VerticalAlignment="Top" HorizontalAlignment="Left" Width="250">
        <ComboBox.ContextMenu>
            <ContextMenu>
                <MenuItem x:Name="MenuItem" Header="Delete" Click="MenuItem_OnClick"></MenuItem>
            </ContextMenu>
        </ComboBox.ContextMenu>
        <TextBlock Text="Item 1"/>
        <TextBlock Text="Item 2"/>
        <TextBlock Text="Item 3"/>
        <TextBlock Text="Item 4"/>
    </ComboBox>
</Grid>

public partial class MainWindow
{
    public MainWindow()
    {
        InitializeComponent();
        CbxItems.PreviewMouseRightButtonDown += OnPreviewMouseRightButtonDown;
    }


    private void OnPreviewMouseRightButtonDown(object sender, MouseButtonEventArgs e)
    {
        var comboBoxItem = VisualUpwardSearch(e.OriginalSource as DependencyObject);

        if (comboBoxItem == null) return;
        comboBoxItem.IsSelected = true;
        e.Handled = true;
    }

    private ComboBoxItem VisualUpwardSearch(DependencyObject source)
    {
        while (source != null && !(source is ComboBoxItem))
            source = VisualTreeHelper.GetParent(source);

        return source as ComboBoxItem;
    }

    private void MenuItem_OnClick(object sender, RoutedEventArgs e)
    {
        CbxItems.Items.Remove(CbxItems.SelectedItem);
    }
}
Nawed Nabi Zada
  • 2,819
  • 5
  • 29
  • 40
1

Put that ContextMenu for each ComboBoxItem instead of the ComboBox itself :

<ComboBoxItem.ContextMenu>
    <ContextMenu>
        <MenuItem Header="Remove" Click="MenuItem_OnClick"></MenuItem>
    </ContextMenu>
</ComboBoxItem.ContextMenu>

You can also put that in DataTemplate or generate it from code behind, depending on how you populate the ComboBox. Then in menu item's click event handler you can do as follow to get user chosen ComboBoxItem :

private void MenuItem_OnClick(object sender, RoutedEventArgs e)
{
    var menuItem = (MenuItem)sender;
    var ctxMenu = (ContextMenu)menuItem.Parent;
    var comboBoxItem = (ComboBoxItem) ctxMenu.PlacementTarget;
}
har07
  • 88,338
  • 12
  • 84
  • 137
0

For locating the combobox items, you can use checkbox in the item template of the combobox so that user can check the items which he/she wants to delete.

If your combobox is data bound, then you will have to filter the datasource of your combobox i.e. on context menu click you will have to delete the items checked by user from the datasource of your combobox and then re-bind the combobox with datasource.

If you don't have a data bound combobox, then on context menu click simply loop through the combobox items and delete the items which are checked by user.

Nitin Joshi
  • 1,638
  • 1
  • 14
  • 17