1

I'm looking to disable the Ctrl key on a ListBox. I followed the example at Disable list box CTRL+C & CTRL+X in D&D . It took some playing, since I'm using System.Windows.Input and using System.Windows.Forms. I was able to resolve the references, except for the e object. It tells me there is no Handled definition within System.Windows.Forms.PreviewKeyDownEventArgs. What am I missing here? Thanks!

  private void lbSigns_PreviewKeyDown(object sender, 
                System.Windows.Forms.PreviewKeyDownEventArgs e)
    {
        //
        // This if statement detects if the Control Key is pressed.
        //
        if ((System.Windows.Input.Keyboard.Modifiers & 
          System.Windows.Input.ModifierKeys.Control) == 
                 System.Windows.Input.ModifierKeys.Control)
        {
            e.Handled = true;
        }
    }

OK, I'm getting closer with @Tsukasa 's code. Here's what I have now.

    List<int> alreadySelectedIndexes = new List<int>();

    private void lbSigns_SelectedIndexChanged(object sender, EventArgs e)
    {

        TrackSelection((ListBox)sender, alreadySelectedIndexes);

        bool allowSelection = false;

        int currentSelectedIndex = -1;

        //make sure we have an item selected
        if (!lbSigns.SelectedIndex.Equals(-1))
        {
            //if first selection we allow it
            if (alreadySelectedIndexes.Count.Equals(1))
            {
                allowSelection = true;
            }
            else
            {
                //get the last item index that was selected from our list
                currentSelectedIndex = alreadySelectedIndexes[alreadySelectedIndexes.Count - 1];

                //make sure we have a previous index item
                if ((currentSelectedIndex - 1) >= 0)
                {
                    //check if previous item before currently selected is checked
                    if (lbSigns.GetSelected(currentSelectedIndex - 1))
                    {
                        allowSelection = true;
                    }
                }

                //make sure we have a next index item
                if ((currentSelectedIndex + 1) <= lbSigns.Items.Count - 1)
                {
                    //check if next item after currently selected is checked
                    if (lbSigns.GetSelected(currentSelectedIndex + 1))
                    {
                        allowSelection = true;
                    }
                }
                //make sure we have both a next and a previous item
                if (((currentSelectedIndex - 1) >= 0) && ((currentSelectedIndex + 1) <= lbSigns.Items.Count - 1))
                {
                    //if both are selected, deny the selection
                    if (lbSigns.GetSelected(currentSelectedIndex - 1) && lbSigns.GetSelected(currentSelectedIndex + 1))
                    {
                        allowSelection = false;
                    }
                }
            }
        }

        //unselect item because it wasn't before or after an already selected item
        if (!allowSelection && !currentSelectedIndex.Equals(-1))
        {
            lbSigns.SetSelected(currentSelectedIndex, false);
        }
    }

    private void TrackSelection(ListBox listBox, List<int> alreadySelectedList)
    {
        ListBox.SelectedIndexCollection indexCollection = listBox.SelectedIndices;

        foreach (int index in indexCollection)
        {
            if (!alreadySelectedList.Contains(index))
            {
                alreadySelectedList.Add(index);
            }
        }

        foreach (int index in new List<int>(alreadySelectedList))
        {
            if (!indexCollection.Contains(index))
            {
                alreadySelectedList.Remove(index);
            }
        }
    }

But, this is letting me de-select an item surrounded by 2 selected items. Once de-selected, it won't let me re-select that item, even though it has selected items on either side of it. I added in the section that follows the comment: //make sure we have both a next and a previous item

Community
  • 1
  • 1
Chuck
  • 203
  • 6
  • 16
  • WPF uses the `Handled` property so the event doesnt bubble up through the parent containers of your control. Winforms events dont work this way, so you dont need to mark the event as handled. – crthompson May 06 '14 at 15:04
  • Don't mix different technologies. In `WPF` and in winforms approach to solve same problem are different. Search for "winforms disable copy" or what is your actual problem, to example, [here](http://stackoverflow.com/q/5113722/1997232) is something. – Sinatr May 06 '14 at 15:12
  • OK @paqogomez, that tells me what not to do. What should I do, then? – Chuck May 06 '14 at 15:13
  • What i'm saying is that nothing is what you should do. With winforms when the event is triggered it goes to the method that handles it and stops. In WPF this is not the case, so they need the `Handled` property. – crthompson May 06 '14 at 15:31

2 Answers2

1

Updated 2

Ok this doesn't allow deselection between selected items, although there is another issue.

Issue: you can hold down the mouse button and ctrl+mouse left and drag up and down. This doesn't call the event until finished. I'm not sure what event to hook to try and correct that.

I do however have a better way I can post later. I would create a global keyboard hook on both the left and right control keys. In the event that fires when that key is detected. If the ListBox has input focus then we handle the key causing only the shift button being able to be used to select multiple items. You want to make sure it's only when input focus is on the ListBox otherwise you would be eating the key for all other apps since it's a global hook.

        //hold already selected items. Last item will be last selected
    List<int> alreadySelectedIndexes = new List<int>();

    //used to skip listBox1_SelectedIndexChanged on TrackSelection
    bool ignoreSelectedChanged = false;

    private void listBox1_SelectedIndexChanged(object sender, EventArgs e)
    {
        //prevent overflow of caused by TrackSelection
        if (!ignoreSelectedChanged)
        {
            TrackSelection((ListBox)sender, alreadySelectedIndexes);

            bool allowSelection = false;

            int currentSelectedIndex = -1;

            //make sure we have an item selected
            if (!listBox1.SelectedIndex.Equals(-1))
            {
                //if first selection we allow it
                if (alreadySelectedIndexes.Count.Equals(1))
                {
                    allowSelection = true;
                }
                else
                {
                    //get the last item index that was selected from our list
                    if ((alreadySelectedIndexes.Count - 1) >= 0)
                    {
                        currentSelectedIndex = alreadySelectedIndexes[alreadySelectedIndexes.Count - 1];

                        //make sure we stay in array range
                        if ((currentSelectedIndex - 1) >= 0)
                        {
                            //check if previous item before currently selected is checked
                            if (listBox1.GetSelected(currentSelectedIndex - 1))
                            {
                                allowSelection = true;
                            }
                        }

                        //make sure we stay in array range
                        if ((currentSelectedIndex + 1) <= listBox1.Items.Count - 1)
                        {
                            //check if next item after currently selected is checked
                            if (listBox1.GetSelected(currentSelectedIndex + 1))
                            {
                                allowSelection = true;
                            }
                        }
                    }
                }

                bool isSelected = false;
                if (currentSelectedIndex >= 0)
                {
                    isSelected = listBox1.GetSelected(currentSelectedIndex);

                    if (!isSelected)
                    {
                        //we can remove it from the list now
                        alreadySelectedIndexes.Remove(currentSelectedIndex);

                        //reselect it if in the middle of already selected items
                        if (alreadySelectedIndexes.Contains(currentSelectedIndex + 1) && alreadySelectedIndexes.Contains(currentSelectedIndex - 1))
                        {
                            ignoreSelectedChanged = true;
                            allowSelection = true;
                            listBox1.SetSelected(currentSelectedIndex, true);
                        }
                    }
                }
            }

            if (!currentSelectedIndex.Equals(-1) && !allowSelection)
            {
                ignoreSelectedChanged = true;
                listBox1.SetSelected(currentSelectedIndex, false);
            }

            //unselect item because it wasn't before or after the last selected item

        }
        ignoreSelectedChanged = false;
    }

    private void TrackSelection(ListBox listBox, List<int> alreadySelectedList)
    {
        ListBox.SelectedIndexCollection indexCollection = listBox.SelectedIndices;

            foreach (int index in indexCollection)
            {
                if (!alreadySelectedList.Contains(index))
                {
                    alreadySelectedList.Add(index);
                }
            }

        foreach (int index in new List<int>(alreadySelectedList))
        {
            if (!indexCollection.Contains(index))
            {
                //remove first index in list
                alreadySelectedList.Remove(index);
                //add index back to end of list so we know what was deselected
                alreadySelectedList.Add(index);
            }
        }
    }
Tsukasa
  • 6,342
  • 16
  • 64
  • 96
  • Then you would use something like this. – Tsukasa May 06 '14 at 15:21
  • @Chuck, with winforms have you seen that something actually HAPPENS when you hit CTRL + C? I think you you are trying to handle an event that is not occurring. You're trying to handle an event that doesnt need handling. – crthompson May 06 '14 at 15:39
  • @Chuck Please update your post with what you are trying to do again and I will get you what you need. Went to lunch and the other post was taken down. You wan't the user to only be able to select items when the previous or next item has already been selected? – Tsukasa May 06 '14 at 16:57
  • Hi @Tsukasa. I have a ListBox with its SelectionMode set to `MultiSimple`. I want users to be able to select either a single item in that ListBox, or a contiguous range of items (i.e. they can use `Shift-Click`), but not individual items (i.e., they can't use `Ctrl-Click`). Let me know if that helps...thanks!!! – Chuck May 06 '14 at 18:00
  • @Chuck So you only want to allow them to use Shift + Click, not Ctrl + Click? – Tsukasa May 06 '14 at 18:02
  • OK, this is really close. It allows me to select items that are next to selected items. Unfortunately, it allows me to un-select an item that is between two selected items, meaning I could end up with a gap in the selections. I have to reboot, will look at this after and let you know. Thanks @Tsukasa !!! – Chuck May 06 '14 at 18:10
  • @Chuck updated. I will post a better solution using a global keyboard hook instead. This will avoid the issue I mention above with the mouse as I don't know what to hook to help correct that issue. – Tsukasa May 06 '14 at 20:57
  • Rats! This is getting out of hand. I was hoping for something nice and simple, like what @Dan put up, too bad it didn't work. I have the option of validating the list when the `Continue` button is pushed and it seems that will be easier. @Tsukasa, if you want to post your solution for academic purposes, feel free to. I'm curious about it, but meanwhile, I'm going to put in my validation. – Chuck May 06 '14 at 21:24
  • @Chuck what you are looking to do isn't native to the control. Although you can't achieve the goal you're looking for with the keyboard hook. I have it tested. Also I think you could prolly override OnDrawItem but I really don't feel like looking into that since the hook solution does everything you want. If you want to use it I can post it. – Tsukasa May 08 '14 at 13:15
  • Hi @Tsukasa . Thanks, but I talked it through with a colleague and we convinced ourselves to take a different approach, one that allows gaps in the selections. I did end up overriding the item's `OnDraw()` to make the text of one item in the list red. I'm curious about the keyboard hook, but don't go to any trouble. When I have a minute, I'll look for other posts on it. Thanks!!! – Chuck May 11 '14 at 18:35
0
            using System;
            using System.Collections.Generic;
            using System.ComponentModel;
            using System.Data;
            using System.Drawing;
            using System.Linq;
            using System.Text;
            using System.Threading.Tasks;
            using System.Windows.Forms;

            namespace listBoxWinforms
            {
                public partial class Form1 : Form
                {
                    public Form1()
                    {
                        InitializeComponent();
                    }

                    private void listBox1_PreviewKeyDown(object sender, PreviewKeyDownEventArgs e)
                    {
                        if (e.Control)
                        {
                            return;
                        }
                    }
                }
            }
Dan
  • 1
  • Place a Breakpoint on the return. Run the application and press Ctrl. You will hit the breakpoint. – Dan May 06 '14 at 15:54
  • I just tried it with a MessageBox before the Return, but the MessageBox doesn't pop up. – Chuck May 06 '14 at 15:54
  • OK, I saw that the PreviewKeyDown event wasn't tied to the handler. Now, when I put in a MessageBox, I get the behavior I'm after. But when I remove the MessageBox, the Ctrl-click is accepted by the listbox. Again, it seems like we're getting closer... – Chuck May 06 '14 at 16:00
  • @Chuck, this answer is still trying to deal with your confusion about `handled`. It doesnt do any testing of whether listbox's that are next to `ListBox1`. To do that we would need more information on the listboxes – crthompson May 06 '14 at 16:02
  • What's wrong with a Breakpoint? MessageBox.Show("Hello"); before return should works too. I suspect you have bigger issues with references in your project. – Dan May 06 '14 at 16:03
  • @papogomez, I'm not t trying to select listboxes that are next to each other. I'm trying to select listbox items that are next to each other. I'm trying to do so by preventing use of the Ctrl key. If they can't use Ctrl, then they can't select items onesy-twosey. – Chuck May 06 '14 at 16:05
  • @Dan, I'm getting the routine to run. When I insert either a breakpoint or a messagebox, I get what I want-no selection if the Ctrl key is pressed. But, when I remove the breakpoint and the messagebox, the item is selected. The behavior changes when the bp & m are not there... – Chuck May 06 '14 at 16:09
  • And, I get this behavior whether the ListBox is set to `MulitSimple` or `MultiExtended`... – Chuck May 06 '14 at 16:13
  • @Chuck And what i'm saying is that in order to do that in winforms you need to query `sender`, find out which listbox it is, then determine if it should be selected. – crthompson May 06 '14 at 16:13
  • Hi, @papogomez I only have one listbox and I'm working in an event handler for that listbox. So, it's not clear why I would have to check where the click is coming from. – Chuck May 06 '14 at 16:16
  • @Chuck, I've misunderstood then and have no clue what you're doing. Perhaps this will be of help.. but i have no idea now. http://stackoverflow.com/a/16214831/2589202 – crthompson May 06 '14 at 16:19
  • Thanks for your time, anyway, @papogomez. I think @Dan is on the right track. It just seems that the selection is made before the `PreviewKeyDown` event fires, but that doesn't make sense. Maybe it needs to `return` differently? – Chuck May 06 '14 at 16:32