C# - How to capture Shift+Del combination event without capturing Shift key only?

1.3k Views Asked by At

I have a textbox and a listbox. Shift must clear the listbox and Shift+Del combination must clear the textbox.

This is my code.

private void MainForm_KeyDown(object sender, KeyEventArgs e)
{
    if (e.Shift && e.KeyCode == Keys.Delete) textBox.Clear();
    if (e.Shift) listBox.Items.Clear();
}

It obviously works wrong because the listbox is always cleared when Shift is down. I need to be able to clear the textbox by pressing Shift+Del and not clear the listbox at the time.

I have to use only Shift and Del keys and no others.

So what do I have to do?

2

There are 2 best solutions below

0
Brian Rogers On BEST ANSWER

First, let me preface this by saying that I would normally advise against using the Shift key alone to trigger an action in any desktop software other than perhaps a game. Doing so violates the Principle of Least Astonishment: since Shift is used routinely in the course of normal typing to modify the meaning of other keys, the user is conditioned to expect that pressing it will not do anything of consequence by itself, and certainly nothing destructive. So if the user casually presses Shift and finds that he has just lost his list of items, he may be quite unpleasantly surprised, particularly if it turns out to be difficult or tedious to undo the damage.

However, you said in the comments that you had no choice in the matter, so with that said, here is a possible solution.

In order to be able to tell the difference between Shift by itself and a Shift + Del combination, we need to capture both the KeyDown and KeyUp events and use the information gleaned from each.

In the KeyDown event, we save the value of the KeyEventArgs.KeyData to a private field, but don't take any action on it yet. KeyData contains the combined Keys flags representing the primary key that was pressed down, along with any modifier keys like Ctrl, Shift or Alt that were held down at the same time. Note that when Shift is pressed by itself, it is both the primary key and a modifier, so the KeyData will contain Keys.ShiftKey (the key) combined with Keys.Shift (the modifier).

private Keys LastKeysDown;

private void MainForm_KeyDown(object sender, KeyEventArgs e)
{
    LastKeysDown = e.KeyData;
}

In the KeyUp event, we look at what keys were released relative to what was last pressed. We are looking for two specific sequences of keys:

  1. Shift + Del is pressed, then Del is released while Shift is still held down.
  2. Shift is pressed with no other key, then released.

If we find what we're looking for, we initiate the appropriate action, otherwise we do nothing.

private void MainForm_KeyUp(object sender, KeyEventArgs e)
{
    // Shift + Delete pressed, then Delete released while Shift still held down
    if (LastKeysDown == (Keys.Shift | Keys.Delete) && e.KeyData == LastKeysDown)
        textBox1.Clear();

    // Shift pressed with no other key, then released
    else if (LastKeysDown == (Keys.Shift | Keys.ShiftKey) && e.KeyData == Keys.ShiftKey)
        listBox1.Items.Clear();

    LastKeysDown = Keys.None;
}

Notice that, as a last order of business, we clear the private LastKeysDown field. This helps to ensure that if we get two KeyUp events in a row, only the first one is paired with the previous KeyDown. But note this is not foolproof: if you hold a key long enough, the computer's key repeat will kick in and generate additional KeyDown events.

2
rene On

If you only want to rely on Key_Down you could use a BackgroundWorker (or a Timer will do as well) that waits for a some milliseconds before it clears the list. If another key comes in during that period, you cancel the BackgroundWorker. Your implementation might look like this:

private void Form1_KeyDown(object sender, KeyEventArgs e)
{
    if (e.Shift && 
        e.KeyCode == Keys.Delete)
    {
        if (backgroundWorker1.IsBusy)
        {
            Trace.WriteLine("cancelling");
            backgroundWorker1.CancelAsync();
        }
        textBox.Clear();
    }

    if (e.Shift && 
        e.KeyCode == Keys.ShiftKey && 
        !backgroundWorker1.IsBusy)
    {
        backgroundWorker1.RunWorkerAsync();
    }

}

private void  backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
    e.Result = false;
    // wait 2 seconds (that is a bit long, adjust accordingly)
    Thread.Sleep(2000); // HACK, use a proper timer here
    // tell RunWorkerCompleted if we're good to clear
    e.Result = !backgroundWorker1.CancellationPending;
}

// this gets called on the UI thread
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    Trace.WriteLine(String.Format("{0} - {1}", e.Cancelled, e.Result));
    if ((bool) e.Result)
    {
        listBox.Items.Clear();
    }
}

If you can also leverage the KeyUp event the solution becomes much easier:

private bool onlyShift = false; // keeps shift state

private void Form1_KeyDown(object sender, KeyEventArgs e)
{
    // becomes true if this is the shift key pressed
    onlyShift = e.Shift && e.KeyCode == Keys.ShiftKey;

    if (e.Shift && 
        e.KeyCode == Keys.Delete)
    {
        textBox.Clear();
    }               
}

private void Form1_KeyUp(object sender, KeyEventArgs e)
{
    // was the shiftkey the last one in keydown AND
    // are we releasing the shiftkey now
    if (onlyShift && e.KeyCode == Keys.ShiftKey)
    {
         listBox.Items.Clear();
    }
}

I would personally use the latter solution.