Как закрыть форму при нажатии клавиши ESC, но только если ее не обработал ни один элемент управления?

У меня есть форма, которая закрывается при нажатии клавиши ESC благодаря KeyPreview, ProcessKeyEventArgs, ProcessCmdKey или чему-то еще. Но у меня есть элемент управления в этой форме, который делает очень важные вещи при нажатии ESC (он прячется), и форма не должна закрываться, когда это происходит.

Элемент управления использует событие KeyDown и устанавливает флаг SuppressKeyPress в значение true, но это происходит после вышеупомянутого предварительного просмотра ключа формы, поэтому не имеет никакого эффекта.

Есть ли какой-то KeyPostview?

Как не закрыть форму, когда элемент управления имеет соответствующее использование нажатия клавиши?

Редактировать. Элемент управления ESC, обрабатывающий ESC, представляет собой текстовое поле, встроенное в ListView ручной работы. Текстовое поле появляется, когда пользователь щелкает ячейку, включая редактирование. Чтобы проверить новый текст, было бы неплохо нажать ENTER (это уже работает, так как фокусируется на чем-то еще). Чтобы отменить выпуск, ESC кажется наиболее естественным.


person Gabriel    schedule 29.12.2010    source источник
comment
Во-первых, я несколько подозрительно отношусь к дизайну. Устроит ли среднестатистический пользователь, что клавиша Esc выполняет две очень разные задачи? Поймут ли они концепцию фокуса и активного контроля достаточно хорошо, чтобы быть в состоянии ожидать, что произойдет? По крайней мере, убедитесь, что вы рассмотрели это с точки зрения пользователя, а не только разработчика.   -  person Cody Gray    schedule 30.12.2010
comment
Вспомните Эксель. Когда вы дважды щелкаете по ячейке, вы можете редактировать ее. ESC отменяет издание.   -  person Gabriel    schedule 30.12.2010
comment
но excel не закрывается, когда вы нажимаете Esc, как вы описали, что делает ваше приложение   -  person David Heffernan    schedule 30.12.2010


Ответы (4)


Вы соревнуетесь по-крупному за клавишу Escape. Наряду с клавишей Enter, это очень важная клавиша в стандартном пользовательском интерфейсе Windows. Просто поместите кнопку на форму и установите свойство CancelButton формы на какую-либо другую кнопку, которая будет сосать нажатие клавиши на эту кнопку.

Чтобы конкурировать с этим, вы должны создать элемент управления, который сообщает Winforms, что вы действительно считаете клавишу Escape более важной. Это требует переопределения свойства IsInputKey. Нравится:

using System;
using System.Windows.Forms;

class MyTexBox : TextBox {
    protected override bool IsInputKey(Keys keyData) {
        if (keyData == Keys.Escape) return true;
        return base.IsInputKey(keyData);
    }
    protected override void OnKeyDown(KeyEventArgs e) {
        if (e.KeyData == Keys.Escape) {
            this.Text = "";   // for example
            e.SuppressKeyPress = true;
            return;
        }
        base.OnKeyDown(e);
    }
}
person Hans Passant    schedule 30.12.2010
comment
Творит чудеса. Однако есть две проблемы. 1. У меня должна быть кнопка на форме для обработки CancelButton. Для меня это не имеет большого значения, потому что он уже есть у меня в виртуальных формах, но это может быть проблемой для других программистов. 2. Эта кнопка должна быть включена и видима. Чтобы избежать путаницы во время разработки, он должен быть хорошо виден во время разработки, а значит, и во время выполнения. Скрыть его, переместив за пределы формы OnLoad, может быть еще одной проблемой. Это по-прежнему лучшее решение для меня сейчас, потому что для закрытия формы не требуется никаких проверок. \о/ - person Gabriel; 30.12.2010
comment
Переопределить ProcessCmdKey() формы. - person Hans Passant; 30.12.2010

ОК - это работает:

class CustomTB : TextBox
{
    public CustomTB()
        : base()
    {
        CustomTB.SuppressEscape = false;
    }

    public static bool SuppressEscape { get; set; }

    protected override void OnKeyDown(KeyEventArgs e)
    {
        CustomTB.SuppressEscape = (e.KeyCode == Keys.Escape);
        base.OnKeyUp(e);
    }
}

В вашей форме:

    public Form1()
    {
        InitializeComponent();
        this.KeyPreview = true;
    }

    private void Form1_KeyUp(object sender, KeyEventArgs e)
    {
        if (e.KeyCode == Keys.Escape && !CustomTB.SuppressEscape)
        {
            this.Close();
        }
        CustomTB.SuppressEscape = false;
    }
person zsalzbank    schedule 29.12.2010
comment
Да, это то, о чем я думал. - person Moose; 30.12.2010
comment
Еще раз, я не хочу проверять этот конкретный элемент управления в Form_KeyUp, это слишком легко забыть. - person Gabriel; 30.12.2010
comment
мой новый код тоже работает. если вы боитесь забыть, то сделайте пользовательский элемент управления. - person zsalzbank; 30.12.2010
comment
Эта новая версия выглядит великолепно! Сразу проверю (:Ну не могу иметь переменную, доступную и для текстбокса, и для формы, так как текстбокс встроен в UserControl, и его событие PreviewKeyDown происходит в другом классе, то есть не в образуют подкласс. - person Gabriel; 30.12.2010
comment
новый код работает так, как вы хотите. нет необходимости проверять каждый элемент управления, он использует статическую переменную во всех пользовательских элементах управления для отслеживания. он должен работать с несколькими одинаковыми пользовательскими элементами управления, потому что только один из них может иметь фокус (и, следовательно, срабатывать) одновременно. - person zsalzbank; 30.12.2010
comment
Это круто. Решение сводилось к проверке статического члена. \o/ Все равно надо проверить. Мне придется вернуть свои старые формы и добавить тест. - person Gabriel; 30.12.2010

Можете ли вы сначала проверить, какой элемент управления имеет фокус? Если в вашей форме есть только один элемент управления, который делает что-то важное с помощью клавиши escape, проверьте, не находится ли фокус на этом элементе управления, прежде чем закрывать форму.

person Moose    schedule 29.12.2010
comment
Я не хочу, чтобы форма проверяла наличие этого конкретного элемента управления (который на самом деле является пользовательским элементом управления). Я хотел бы иметь возможность сбросить этот элемент управления на форму и, ничего не делая, ожидать, что он будет работать правильно. Однако я готов изменить KeyPreview, который выглядит как тупик. - person Gabriel; 30.12.2010
comment
Хорошо. Кажется, что 2 строки сделают это за вас, не может быть намного проще. В любом случае, я должен согласиться с Коди выше. - person Moose; 30.12.2010

Основная проблема заключается в том, что метод Dispose формы вызывается при вызове Close, поэтому форма закроется, и вы мало что можете с этим поделать.

Я бы обошёл это, реализовав в UserControl интерфейс маркера, скажем, ISuppressEsc. Затем обработчик KeyUp формы может найти текущий элемент управления и отменить закрытие, если сфокусированный элемент управления реализует ISuppressEsc. Имейте в виду, что вам придется проделать дополнительную работу, чтобы найти сфокусированный элемент управления, если он может быть вложенным элементом управления.

public interface ISuppressEsc
{
    // marker interface, no declarations
}

public partial class UserControl1 : UserControl, ISuppressEsc
{
    public UserControl1()
    {
        InitializeComponent();
    }

    private void textBox1_KeyUp(object sender, KeyEventArgs e)
    {
        if (e.KeyCode == Keys.Escape)
        {
            textBox1.Text = DateTime.Now.ToLongTimeString();
        }
    }
}

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
        KeyPreview = true;
    }

    private void Form1_KeyUp(object sender, KeyEventArgs e)
    {
        var activeCtl = ActiveControl;
        if (!(activeCtl is ISuppressEsc) && e.KeyCode == Keys.Escape)
        {
            Close();
        }
    }
}
person Jamie Ide    schedule 29.12.2010