I have a multiselection listbox. I want to see the last selected item (which the user just selected). I can't find any way to do this googling..

Thanks in advance.

Dani AI

Generated

A compact expert note to complement the thread: the core choices are (A) track selection-state differences (the approach implemented) or (B) capture the user interaction that caused the change (simpler and usually more reliable for mouse-driven input). 's point about separating the logic out of the form is good practice — keep selection-tracking code in a small helper/monitor so the UI code stays clean.

A lightweight WinForms technique that avoids full list-diffing is to record the item under the mouse when the click happens, then use the normal SelectedIndexChanged/SelectedValueChanged to react. This also keeps mouse- and keyboard-driven flows separate:

string lastClickedItem = null;

private void listBox1_MouseDown(object sender, MouseEventArgs e)
{
    int idx = listBox1.IndexFromPoint(e.Location);
    if (idx >= 0) lastClickedItem = listBox1.Items[idx].ToString();
}

private void listBox1_SelectedIndexChanged(object sender, EventArgs e)
{
    // lastClickedItem contains the item last clicked with the mouse;
    // fallback to a diff or keyboard handler when null.
    Debug.WriteLine("Last changed: " + (lastClickedItem ?? "<keyboard/fallback>"));
    lastClickedItem = null;
}

For web (HTML <select multiple>) a hybrid is safest: try to capture the clicked option with pointerdown (when available) and fall back to diffing previous/current selected sets on change. Example pattern:

const sel = document.querySelector('select[multiple]');
let lastClicked = null;
let previous = new Set([...sel.options].filter(o => o.selected).map(o => o.value || o.text));

sel.addEventListener('pointerdown', e => {
  if (e.target.tagName === 'OPTION') lastClicked = e.target.value || e.target.text;
});

sel.addEventListener('change', () => {
  const current = new Set([...sel.options].filter(o => o.selected).map(o => o.value || o.text));
  const added = [...current].filter(x => !previous.has(x));
  const removed = [...previous].filter(x => !current.has(x));
  const last = lastClicked || (added.length ? added[added.length-1] : (removed[0] || null));
  console.log('last changed:', last);
  previous = current;
  lastClicked = null;
});

Caveats: shift-range selects may produce multiple additions so "last" can be ambiguous unless a pointer event is available; keyboard-only users need a fallback (diff or explicit keyboard handlers); native multi-select is poor on mobile — checkboxes or a custom list UI (as discovered) are often the better UX.

Recommended Answers

All 5 Replies

Here's a solution. Version 2 code has been tested.

Create a new class called "ItemInfo". Replace with the following code:

ItemInfo:

    public class ItemInfo
    {
        public string Name { get; set; }
        public string SelectionType { get; set; }
    }

In your form, (ex: Form1.cs), do the following:

Create a list to hold the previously selected items and an instance of ItemInfo.

    public partial class Form1 : Form
    {
        List<string> previousSelectedItemsList = new List<string>();
        ItemInfo lastItem = new ItemInfo();

        public Form1()
        {
            InitializeComponent();
        }
    }

Then we create a method to find the last selected item:

Version 1
(keeps track of last selected item):

private void getLastItemChanged(ref ListBox lb)
{
    List<string> lastItemSelected;


    List<string> currentSelectedItemsList = new List<string>();

    //put selected items into a list
    //so we can use list.except

    for (int i = 0; i < lb.SelectedItems.Count; i++)
    {
        //add selected items to list
        currentSelectedItemsList.Add(lb.SelectedItems[i].ToString());

        //Console.WriteLine("item: " + lb.SelectedItems[i]);
    }//for

    //get last item selected as a list
    //by comparing the current list
    //to the previous list

    lastItemSelected = currentSelectedItemsList.Except(previousSelectedItemsList).ToList();

    //if last item was selected count > 0
    //else if last item was de-selected 
    //lastItemSelected.Count = -1

    if (lastItemSelected.Count > 0)
    {
        lastItem.Name = lastItemSelected[0].ToString();
        lastItem.SelectionType = "selected";
    }//if


     Console.WriteLine(lastItem.Name + " : " + lastItem.SelectionType);

     //updated previousSelectedItems before returning
     previousSelectedItemsList = currentSelectedItemsList;
}//getLastItemChanged

Version 2
(keeps track of last item - selected OR de-selected):

private void getLastItemChanged(ref ListBox lb)
{
    List<string> lastItemSelected;
    List<string> lastItemDeSelected;

    List<string> currentSelectedItemsList = new List<string>();

    //put selected items into a list
    //so we can use list.Except
    for (int i = 0; i < lb.SelectedItems.Count; i++)
    {
        //add selected items to list
        currentSelectedItemsList.Add(lb.SelectedItems[i].ToString());

        //Console.WriteLine("item: " + lb.SelectedItems[i]);
    }//for

    //get last item selected as a list
    //by comparing the current list
    //to the previous list

    lastItemSelected = currentSelectedItemsList.Except(previousSelectedItemsList).ToList();

    //if last item was selected count > 0
    //else if last item was de-selected 
    //lastItemSelected.Count = -1

    if (lastItemSelected.Count > 0)
    {
        lastItem.Name = lastItemSelected[0].ToString();
        lastItem.SelectionType = "selected";
    }//if
    else
    {
        //if item was deselected, switch the order
        //compare previous list to current list
        lastItemDeSelected = previousSelectedItemsList.Except(currentSelectedItemsList).ToList();

        //if > 0, last item was de-selected
        if (lastItemDeSelected.Count > 0)
        {
            lastItem.Name = lastItemDeSelected[0].ToString();
            lastItem.SelectionType = "de-selected";
        }//if
     }//else

     Console.WriteLine(lastItem.Name + " : " + lastItem.SelectionType);

     //updated previousSelectedItems before returning
     previousSelectedItemsList = currentSelectedItemsList;
}//getLastItemChanged

Then in the ListBox SelectedValueChanged event handler (ex: listBox1_SelectedValueChanged), we call "getLastItemChanged":

private void listBox1_SelectedValueChanged(object sender, EventArgs e)
{
    getLastItemChanged(ref listBox1);
}

List.Except code adapted from here.

I re-worked the above code to create a version that allows you to specify if you want the last "Selected" item, the last "De-selected" item, or last item that was changed (either "Selected" or "De-selected").

Create a new class called "ItemInfo". Replace the code with the following:
ItemInfo:

public class ItemInfo
{
    public string Name { get; set; }
    public string SelectionType { get; set; }
}

In the form (Form1), create a list, a new instance of ItemInfo, and an enum as seen below:

public partial class Form1 : Form
{
    //holds previously selected items
    List<string> previousSelectedItemsList = new List<string>();

    //holds the last selected and/or de-selected item
    ItemInfo lastItem = new ItemInfo();

    //create an enumeration 
    enum SelectionType { Deselected, Either, Selected }; 

    public Form1()
    {
        InitializeComponent();
    }
}

Now, we will create a method that will compare our lists and return an ItemInfo object:

private ItemInfo findExcludedItemInList(List<string> list1, List<string> list2)
{
    ItemInfo item = new ItemInfo();

    //get list of items in list1 that aren't in list2
    var lastItemList1 = list1.Except(list2).ToList();

    if (lastItemList1.Count > 0)
    {
        //there should only be 1 item in the list
        item.Name = lastItemList1[0].ToString();
        item.SelectionType = "Selected";
    }//if
    else
    {
        //get list of items in list2 that aren't in list1
        var lastItemList2 = list2.Except(list1).ToList();

        if (lastItemList2.Count > 0)
        {
            //there should only be 1 item in the list
            item.Name = lastItemList2[0].ToString();
            item.SelectionType = "Deselected";
        }//if
    }//else

    return item;
}//findExcludedItemInList

Next, we will create a method "getLastItemChanged" that will use "findExcludedItemInList".

private void getLastItemChanged(ref ListBox lb,  ref ItemInfo lastItem, ref List<string> previousSelectedItemsList, SelectionType sType)
{

    List<string> currentSelectedItemsList = new List<string>();

    //put selected items into a list
    //so we can use list.Except
    for (int i = 0; i < lb.SelectedItems.Count; i++)
    {
        currentSelectedItemsList.Add(lb.SelectedItems[i].ToString());
        //Console.WriteLine("item: " + lb.SelectedItems[i]);
    }//for

    if (sType.Equals(SelectionType.Either))
    {
        lastItem = findExcludedItemInList(currentSelectedItemsList, 
                                          previousSelectedItemsList);

        Console.WriteLine(lastItem.Name + " : " + lastItem.SelectionType);
    }//if
    else if (sType.Equals(SelectionType.Selected))
    {
        //get last item selected as a list
        //by comparing the current list
        //to the previous list

        if (String.Compare(findExcludedItemInList(currentSelectedItemsList,
                           previousSelectedItemsList).SelectionType, "Selected", true) == 0)
        {
            lastItem = findExcludedItemInList(currentSelectedItemsList,
                                              previousSelectedItemsList);

            Console.WriteLine(lastItem.Name + " : " + lastItem.SelectionType);
        }//if
    }//else if
    else if (sType.Equals(SelectionType.Deselected))
    {
        //if item was deselected, switch the order
        //compare previous list to current list

        //if > 0, last item was de-selected
        if (String.Compare(findExcludedItemInList(currentSelectedItemsList, 
                           previousSelectedItemsList).SelectionType, "Deselected", true) == 0)
        {
            lastItem = findExcludedItemInList(currentSelectedItemsList, 
                                              previousSelectedItemsList);

            Console.WriteLine(lastItem.Name + " : " + lastItem.SelectionType);
        }//if
    }//else if


    //updated previousSelectedItems before returning
    previousSelectedItemsList = currentSelectedItemsList;
}//getLastItemChanged

Then in the ListBox SelectedValueChanged event handler (ex: listBox1_SelectedValueChanged), we call "getLastItemChanged":

private void listBox1_SelectedValueChanged(object sender, EventArgs e)
{
    //get only last selected item
    //getLastItemChanged(ref listBox1, ref lastItem, ref previousSelectedItemsList, SelectionType.Selected);

    //get only last Deselected item
    //getLastItemChanged(ref listBox1, ref lastItem, ref previousSelectedItemsList, SelectionType.Deselected);

    //get last changed item - Selected or Deselected
    getLastItemChanged(ref listBox1, ref lastItem, ref previousSelectedItemsList, SelectionType.Either);

    Console.WriteLine("lastItem: " + lastItem.Name + " Type: " + lastItem.SelectionType);
}
commented: Great effort +15
commented: Bad coding standards, numerous ref used unnecessarily. No SoC. "But it works" is not something you should aim for. -2

There is no need to pass any of that stuff by reference, remove it all. You mix modes (windows controls writing to the console). Your methods have overly complicated signatures and almost all that code (if not all of it) should be inside a different class, not in your form. It is a ListBox monitor, code it as such.

commented: +1 +11

Tough crowd. The Console.WriteLine statements were for debugging. I left them in so that one could verify the results. They should be removed when finished debugging.

Here is an updated version:
The concept of the code is the same. However, I moved the majority of the code to it's own class: ListBoxMonitor. "getLastItemChanged" now returns an "ItemInfo" object. I removed some parameters from "getLastItemChanged" and made them private variables in class ListBoxMonitor. I've also made ListBoxMonitor static.

Create a new class called "ListBoxMonitor.cs" and replace with the following code:

    public static class ListBoxMonitor
    {
        //holds the last item that was changed
        //based on SelectionType chosen by user
        private static ItemInfo lastItem = new ItemInfo();

        //holds the previously selected list items
        private static List<string> previousSelectedItemsList = new List<string>();

        //create an enumeration 
        public enum SelectionType { Deselected, Either, Selected }; 


        public static ItemInfo getLastItemChanged(System.Windows.Forms.ListBox lb, SelectionType sType)
        {

            List<string> currentSelectedItemsList = new List<string>();

            //put selected items into a list
            //so we can use list.Except
            for (int i = 0; i < lb.SelectedItems.Count; i++)
            {
                currentSelectedItemsList.Add(lb.SelectedItems[i].ToString());
            }//for

            if (sType.Equals(SelectionType.Either))
            {
                lastItem = findExcludedItemInList(currentSelectedItemsList,
                                                  previousSelectedItemsList);
            }//if
            else if (sType.Equals(SelectionType.Selected))
            {
                //get last item selected as a list
                //by comparing the current list
                //to the previous list

                if (String.Compare(findExcludedItemInList(currentSelectedItemsList,
                    previousSelectedItemsList).SelectionType, "Selected", true) == 0)
                {
                    lastItem = findExcludedItemInList(currentSelectedItemsList,
                               previousSelectedItemsList);
                }//if
            }//else if
            else if (sType.Equals(SelectionType.Deselected))
            {
                //if item was deselected, switch the order
                //compare previous list to current list

                //if > 0, last item was de-selected
                if (String.Compare(findExcludedItemInList(currentSelectedItemsList,
                    previousSelectedItemsList).SelectionType, "Deselected", true) == 0)
                {
                    lastItem = findExcludedItemInList(currentSelectedItemsList,
                                                      previousSelectedItemsList);
                }//if
            }//else if



            //updated previousSelectedItems before returning
            previousSelectedItemsList = currentSelectedItemsList;

            return lastItem;
        }//getLastItemChanged

        private static ItemInfo findExcludedItemInList(List<string> list1, List<string> list2)
        {
            ItemInfo item = new ItemInfo();

            //get list of items in list1 that aren't in list2
            var lastItemList1 = list1.Except(list2).ToList();

            if (lastItemList1.Count > 0)
            {
                //there should only be 1 item in the list
                item.Name = lastItemList1[0].ToString();
                item.SelectionType = "Selected";
            }//if
            else
            {
                //get list of items in list2 that aren't in list1
                var lastItemList2 = list2.Except(list1).ToList();

                if (lastItemList2.Count > 0)
                {
                    //there should only be 1 item in the list
                    item.Name = lastItemList2[0].ToString();
                    item.SelectionType = "Deselected";
                }//if
            }//else

            return item;
        }//findExcludedItemInList

    }//class

Usage:

    public partial class Form1 : Form
    {

        ItemInfo lastItem = null;

        public Form1()
        {
            InitializeComponent();
        }


        private void listBox1_SelectedValueChanged(object sender, EventArgs e)
        {
            //get only last selected item
            //lastItem = ListBoxMonitor.getLastItemChanged(listBox1, ListBoxMonitor.SelectionType.Selected);

            //get only last Deselected item
            //lastItem = ListBoxMonitor.getLastItemChanged(listBox1, ListBoxMonitor.SelectionType.Deselected);

            //get last changed item - Selected or Deselected
            lastItem = ListBoxMonitor.getLastItemChanged(listBox1, ListBoxMonitor.SelectionType.Either);

        }
    }

I remembered that I can use CheckedBox instead.
Thanks all and I studied your code and learned new things.

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.