What event handler to use for ComboBox Item Selected (Selected Item not necessarily changed)

C#Wpf

C# Problem Overview


Goal: issue an event when items in a combobox drop down list is selected.

Problem: Using "SelectionChanged", however, if the user choose the same item as the item is currently being selected then the selection is not changed and therefore this event will not be triggered.

Question: What other event handler(or other ways) I may use to issue an event regardless of the selected item is changed or not as long as the mouse clicked on that item and that item is being selected.

(Clarification: Problem is how to trigger "something" when the same item is being selected again. No duplicates in the drop down list. Scenario: first time select item 1,close drop down. And then again, open drop down box and select item 1 when some function are triggered.)

Solution: for now there seems to be no straight-forward solution to do this. But according to each individual project, there can be ways to work around it. (Please update if there are indeed good ways to do this). Thanks.

C# Solutions


Solution 1 - C#

I had the same question and I finally found the answer:

You need to handle BOTH the SelectionChanged event and the DropDownClosed like this:

In XAML:

<ComboBox Name="cmbSelect" SelectionChanged="ComboBox_SelectionChanged" DropDownClosed="ComboBox_DropDownClosed">
    <ComboBoxItem>1</ComboBoxItem>
    <ComboBoxItem>2</ComboBoxItem>
    <ComboBoxItem>3</ComboBoxItem>
</ComboBox>

In C#:

private bool handle = true;
private void ComboBox_DropDownClosed(object sender, EventArgs e) {
  if(handle)Handle();
  handle = true;
}

private void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e) {
  ComboBox cmb = sender as ComboBox;
  handle = !cmb.IsDropDownOpen;
  Handle();
}

private void Handle() {
  switch (cmbSelect.SelectedItem.ToString().Split(new string[] { ": " }, StringSplitOptions.None).Last())
  { 
      case "1":
          //Handle for the first combobox
          break;
      case "2":
          //Handle for the second combobox
          break;
      case "3":
          //Handle for the third combobox
          break;
  }
}

Solution 2 - C#

For me ComboBox.DropDownClosed Event did it.

private void cbValueType_DropDownClosed(object sender, EventArgs e)
    {
        if (cbValueType.SelectedIndex == someIntValue) //sel ind already updated
        {
            // change sel Index of other Combo for example
            cbDataType.SelectedIndex = someotherIntValue;
        }
    }

Solution 3 - C#

You can use "ComboBoxItem.PreviewMouseDown" event. So each time when mouse is down on some item this event will be fired.

To add this event in XAML use "ComboBox.ItemContainerStyle" like in next example:

   <ComboBox x:Name="MyBox"
        ItemsSource="{Binding MyList}"
        SelectedValue="{Binding MyItem, Mode=OneWayToSource}" >
        <ComboBox.ItemContainerStyle>
            <Style>
                <EventSetter Event="ComboBoxItem.PreviewMouseDown"
                    Handler="cmbItem_PreviewMouseDown"/>
            </Style>
        </ComboBox.ItemContainerStyle>
   </ComboBox>

and handle it as usual

void cmbItem_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
    //...do your item selection code here...
}

Thanks to [MSDN][1]

[1]: http://social.msdn.microsoft.com/Forums/vstudio/en-US/b3fdc20d-a6ee-4b38-a534-c5cc5a0df6fc/combobox-fire-selectionchanged-when-reselecting-same-item?forum=wpf "MSDN"

Solution 4 - C#

I hope that you will find helpfull the following trick.

You can bind both the events

combobox.SelectionChanged += OnSelectionChanged;
combobox.DropDownOpened += OnDropDownOpened;

And force selected item to null inside the OnDropDownOpened

private void OnDropDownOpened(object sender, EventArgs e)
{
    combobox.SelectedItem = null;
}

And do what you need with the item inside the OnSelectionChanged. The OnSelectionChanged will be raised every time you will open the combobox, but you can check if SelectedItem is null inside the method and skip the command

private void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        if (combobox.SelectedItem != null)
        {
           //Do something with the selected item
        }
    }

Solution 5 - C#

For UWP (Windows Store) apps none of the above will work (PointerPressed doesn't fire; no Preview, DropDownClosed or SelectedIndexChanged events exist)

I had to resort to a transparent button overlaying the ComboBox (but not its drop down arrow). When you press on the arrow, the list drops down as usual and the Combo Box's SelectionChanged event fires. When you click anywhere else on the Combo Box the transparent button's click event fires allowing you to re-select the Combo Box's current value.

Some working XAML code:

                <Grid x:Name="ComboOverlay" Margin="0,0,5,0"> <!--See comments in code behind at ClickedComboButValueHasntChanged event handler-->
                    <ComboBox x:Name="NewFunctionSelect" Width="97" ItemsSource="{x:Bind Functions}"
                          SelectedItem="{x:Bind ChosenFunction}" SelectionChanged="Function_SelectionChanged"/>
                    <Button x:Name="OldFunctionClick" Height="30" Width="73" Background="Transparent" Click="ClickedComboButValueHasntChanged"/>
                </Grid>

Some working C# code:

    /// <summary>
    /// It is impossible to simply click a ComboBox to select the shown value again. It always drops down the list of options but
    ///     doesn't raise SelectionChanged event if the value selected from the list is the same as before
    ///     
    /// To handle this, a transparent button is overlaid over the ComboBox (but not its dropdown arrow) to allow reselecting the old value
    /// Thus clicking over the dropdown arrow allows the user to select a new option from the list, but
    ///     clicking anywhere else in the Combo re-selects the previous value
    /// </summary>
    private void ClickedComboButValueHasntChanged(object sender, RoutedEventArgs e)
    {
        //You could also dummy up a SelectionChangedEvent event and raise it to invoke Function_SelectionChanged handler, below 
        FunctionEntered(NewFunctionSelect.SelectedValue as string);
    }

    private void Function_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        FunctionEntered(e.AddedItems[0] as string);
    }

Solution 6 - C#

You can try "SelectedIndexChanged", it will trigger the event even if the same item is selected.

Solution 7 - C#

For UWP, I tried a different approach. I extended the ComboBox class, and processed the SelectionChanged and OnKeyUp events on the ComboBox as well as the Tapped event on the ComboBoxItems. In cases where I get a Tapped event or an Enter or Space key without first getting a SelectionChanged then I know the current item has been re-selected and I respond accordingly.

class ExtendedComboBox : ComboBox
{
    public ExtendedComboBox()
    {
        SelectionChanged += OnSelectionChanged;
    }

    protected override void PrepareContainerForItemOverride(Windows.UI.Xaml.DependencyObject element, object item)
    {
        ComboBoxItem cItem = element as ComboBoxItem;
        if (cItem != null)
        {
            cItem.Tapped += OnItemTapped;
        }

        base.PrepareContainerForItemOverride(element, item);
    }

    protected override void OnKeyUp(KeyRoutedEventArgs e)
    {
        // if the user hits the Enter or Space to select an item, then consider this a "reselect" operation
        if ((e.Key == Windows.System.VirtualKey.Space || e.Key == Windows.System.VirtualKey.Enter) && !isSelectionChanged)
        {
            // handle re-select logic here
        }

        isSelectionChanged = false;

        base.OnKeyUp(e);
    }

    // track whether or not the ComboBox has received a SelectionChanged notification
    // in cases where it has not yet we get a Tapped or KeyUp notification we will want to consider that a "re-select"
    bool isSelectionChanged = false;
    private void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        isSelectionChanged = true;
    }

    private void OnItemTapped(object sender, TappedRoutedEventArgs e)
    {
        if (!isSelectionChanged)
        {
            // indicates that an item was re-selected  - handle logic here
        }

        isSelectionChanged = false;
    }
}

Solution 8 - C#

This problem bugs me for a long time since none of the work-around worked for me :(

But good news is, the following method works fine for my application.

The basic idea is to register a EventManager in App.xmal.cs to sniff PreviewMouseLeftButtonDownEvent for all ComboBoxItem, then trigger the SelectionChangedEvent if the selecting item is the same as the selected item, i.e. the selection is performed without changing index.

In App.xmal.cs:

public partial class App : Application
{
    protected override void OnStartup(StartupEventArgs e)
    {
        // raise selection change event even when there's no change in index
        EventManager.RegisterClassHandler(typeof(ComboBoxItem), UIElement.PreviewMouseLeftButtonDownEvent,
                                          new MouseButtonEventHandler(ComboBoxSelfSelection), true);

        base.OnStartup(e);
    }

    private static void ComboBoxSelfSelection(object sender, MouseButtonEventArgs e)
    {
        var item = sender as ComboBoxItem;

        if (item == null) return;

        // find the combobox where the item resides
        var comboBox = ItemsControl.ItemsControlFromItemContainer(item) as ComboBox;

        if (comboBox == null) return;

        // fire SelectionChangedEvent if two value are the same
        if ((string)comboBox.SelectedValue == (string)item.Content)
        {
            comboBox.IsDropDownOpen = false;
            comboBox.RaiseEvent(new SelectionChangedEventArgs(Selector.SelectionChangedEvent, new ListItem(), new ListItem()));
        }
    }
}

Then, for all combo boxes, register SelectionChangedEvent in a normal way:

<ComboBox ItemsSource="{Binding BindList}"
          SelectionChanged="YourSelectionChangedEventHandler"/>

Now, if two indices are different, nothing special but the ordinary event handling process; if two indices are the same, the Mouse Event on the item will first be handled, and thus trigger the SelectionChangedEvent. In this way, both situations will trigger SelectionChangedEvent :)

Solution 9 - C#

This is a DependencyObject for attaching to a ComboBox.

It records the currently selected item when the dropdown is opened, and then fires SelectionChanged event if the same index is still selected when the dropdown is closed. It may need to be modified for it to work with Keyboard selection.

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Web.UI.WebControls;

namespace MyNamespace 
{
    public class ComboAlwaysFireSelection : DependencyObject
    {
        public static readonly DependencyProperty ActiveProperty = DependencyProperty.RegisterAttached(
            "Active",
            typeof(bool),
            typeof(ComboAlwaysFireSelection),
            new PropertyMetadata(false, ActivePropertyChanged));

        private static void ActivePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var element = d as ComboBox;
            if (element == null) 
                return;

            if ((e.NewValue as bool?).GetValueOrDefault(false))
            {
                element.DropDownClosed += ElementOnDropDownClosed;
                element.DropDownOpened += ElementOnDropDownOpened;
            }
            else
            {
                element.DropDownClosed -= ElementOnDropDownClosed;
                element.DropDownOpened -= ElementOnDropDownOpened;
            }
        }

        private static void ElementOnDropDownOpened(object sender, EventArgs eventArgs)
        {
            _selectedIndex = ((ComboBox) sender).SelectedIndex;
        }

        private static int _selectedIndex;

        private static void ElementOnDropDownClosed(object sender, EventArgs eventArgs)
        {
            var comboBox = ((ComboBox) sender);
            if (comboBox.SelectedIndex == _selectedIndex)
            {
                comboBox.RaiseEvent(new SelectionChangedEventArgs(Selector.SelectionChangedEvent, new ListItemCollection(), new ListItemCollection()));
            }
        }

        [AttachedPropertyBrowsableForChildrenAttribute(IncludeDescendants = false)]
        [AttachedPropertyBrowsableForType(typeof(ComboBox))]
        public static bool GetActive(DependencyObject @object)
        {
            return (bool)@object.GetValue(ActiveProperty);
        }

        public static void SetActive(DependencyObject @object, bool value)
        {
            @object.SetValue(ActiveProperty, value);
        }
    }
}

and add your namespace prefix to make it accessible.

<UserControl xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:ut="clr-namespace:MyNamespace" ></UserControl>

and then you need to attach it like so

<ComboBox ut:ComboAlwaysFireSelection.Active="True" />

Solution 10 - C#

Use SelectionChangeCommitted(object sender, EventArgs e) event here

Solution 11 - C#

Every ComboBoxItem instance has PreviewMouseDown event. If you will subscribe your custom handler on this event on every ComboBoxItem you will have opportunity to handle every click on the dropdown list.

// Subscribe on ComboBoxItem-s events.
comboBox.Items.Cast<ComboBoxItem>().ToList().ForEach(i => i.PreviewMouseDown += ComboBoxItem_PreviewMouseDown);

private void ComboBoxItem_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
     // your handler logic...         
}

Solution 12 - C#

It's easy, just add if (e.AddedItems.Count == 0) return; in the beggining of function like:

        private  void ComboBox_Symbols_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        if (e.AddedItems.Count == 0) 
        return;
        //Some Other Codes
    }

Solution 13 - C#

private void OnDropDownClosed(object sender, EventArgs e)
{ 
     if (combobox.SelectedItem == null) return;
     // Do actions
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestiongrcView Question on Stackoverflow
Solution 1 - C#deltonio2View Answer on Stackoverflow
Solution 2 - C#DanielView Answer on Stackoverflow
Solution 3 - C#Vadim LevkovskyView Answer on Stackoverflow
Solution 4 - C#Gianluca ConteView Answer on Stackoverflow
Solution 5 - C#RobertView Answer on Stackoverflow
Solution 6 - C#Vas VasanthView Answer on Stackoverflow
Solution 7 - C#Rich SView Answer on Stackoverflow
Solution 8 - C#nevetsView Answer on Stackoverflow
Solution 9 - C#jv_View Answer on Stackoverflow
Solution 10 - C#ZackView Answer on Stackoverflow
Solution 11 - C#Evgeniy MiroshnichenkoView Answer on Stackoverflow
Solution 12 - C#Esmaeel MotaghiView Answer on Stackoverflow
Solution 13 - C#Юрий КоноваловView Answer on Stackoverflow