Как захватить событие комбинации Shift+Del, не захватывая только клавишу Shift?

У меня есть текстовое поле и список. Shift должен очистить список, а комбинация Shift+Del должна очистить текстовое поле.

Это мой код

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

Очевидно, это работает неправильно, потому что список всегда очищается, когда Shift выключен. Мне нужно иметь возможность очистить текстовое поле, нажав Shift+Del и не очистить список в то время.

Я должен использовать только клавиши Shift и Del, а не другие.

Так что мне делать?

2 ответа

Решение

Во-первых, позвольте мне предвосхитить это, сказав, что я обычно советую не использовать одну только клавишу Shift для запуска действия в любом программном обеспечении на компьютере, кроме, возможно, игры. Это нарушает принцип наименьшего удивления: поскольку Shift обычно используется в ходе обычной печати для изменения значения других клавиш, пользователь должен ожидать, что нажатие на него само по себе не будет иметь никаких последствий, и, конечно же, ничего разрушительного, Таким образом, если пользователь случайно нажимает Shift и обнаруживает, что он только что потерял свой список предметов, он может быть довольно неприятно удивлен, особенно если оказывается, что трудно или утомительно исправить ущерб.

Тем не менее, вы сказали в комментариях, что у вас не было выбора в этом вопросе, поэтому с учетом сказанного, вот возможное решение.

Чтобы можно было различить разницу между Shift и комбинацией Shift + Del, нам нужно KeyDown а также KeyUp События и использовать информацию, почерпнутую из каждого.

в KeyDown событие, мы сохраняем значение KeyEventArgs.KeyData в личное поле, но пока не предпринимайте никаких действий. KeyData содержит комбинированный Keys флаги, представляющие первичную клавишу, которая была нажата, наряду с любыми клавишами-модификаторами, такими как Ctrl, Shift или Alt, которые удерживались одновременно. Обратите внимание, что когда Shift нажимается сам по себе, это и первичная клавиша, и модификатор, поэтому KeyData будет содержать Keys.ShiftKey (ключ) в сочетании с Keys.Shift (модификатор).

private Keys LastKeysDown;

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

в KeyUp событие, мы посмотрим, какие клавиши были отпущены относительно того, что было в последний раз нажал. Мы ищем две конкретные последовательности ключей:

  1. Нажмите Shift + Del, затем Del отпустите, пока Shift все еще удерживается.
  2. Shift нажимается без другой клавиши, затем отпускается.

Если мы находим то, что ищем, мы инициируем соответствующее действие, в противном случае мы ничего не делаем.

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;
}

Обратите внимание, что, как последний порядок бизнеса, мы очищаем частный LastKeysDown поле. Это помогает гарантировать, что если мы получим два KeyUp события подряд, только первое сопряжено с предыдущим KeyDown, Но учтите, что это не является надежной защитой: если вы держите клавишу достаточно долго, сработает повтор клавиатуры, и вы получите дополнительные KeyDown События.

Если вы хотите только положиться Key_Down Вы могли бы использовать BackgroundWorker (или Таймер также сделает), который ждет несколько миллисекунд, прежде чем очистить список. Если в этот период поступит другой ключ, вы отмените BackgroundWorker. Ваша реализация может выглядеть так:

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();
    }
}

Если вы также можете использовать событие KeyUp, решение становится намного проще:

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();
    }
}

Я бы лично использовал последнее решение.

Другие вопросы по тегам