MenuStrip не скрывается после щелчка по элементу. Ошибка?

Я испытываю очень странное поведение с MenuStrip:

  1. Создайте новый проект WinForms (.net 4.0. C # или VB.NET не имеет значения; я использовал C #).
  2. Поместите MenuStrip в форму по умолчанию. Щелкните правой кнопкой мыши и выберите Вставить стандартные элементы, чтобы быстро создать его для вас. Этот шаг также можно сделать вручную.
  3. Также добавьте OpenFileDialog в свою форму.
  4. Добавьте обработчик события DropDownItemClicked в меню «Файл». Добавьте в него следующий код:

    private void fileToolStripMenuItem_DropDownItemClicked(object sender, ToolStripItemClickedEventArgs e)
    {
        if (e.ClickedItem.Name == "openToolStripMenuItem")
        {
            if (openFileDialog1.ShowDialog() == System.Windows.Forms.DialogResult.OK)
                MessageBox.Show(openFileDialog1.FileName);
        }
    }
    
  5. Запустите проект. Щелкните меню Файл, а затем команду Открыть. Появляется диалоговое окно с файлом, НО меню Файл не исчезает. Фактически, он рисует поверх OpenFileDialog, скрывая какую-то его часть. После того, как вы нажмете Открыть или Отмена в диалоговом окне, диалоговое окно и меню Файл исчезнут.

Почему это так? Это известная ошибка или функция? Я также проверил, что этого не происходит для моих диалоговых окон, только для встроенных диалогов. Перед отображением встроенных диалоговых окон необходимо вручную вызвать FileToolStripMenuItem.HideDropDown().


person dotNET    schedule 28.09.2013    source источник


Ответы (1)


Это не ошибка. Это особенность.

Фактически выпадающее меню будет автоматически скрыто после выполнения кода в обработчике событий DropDownItemClicked. Однако вы используете какой-то MessageBox или ShowDialog, который блокирует текущее выполнение и вешает выпадающее меню там.

Есть как минимум два решения этой проблемы, одно из них - скрыть меню самостоятельно перед отображением диалогового окна (похоже, вы приняли его). Другое решение - использовать BeginInvoke для отображения вашего диалога, этот вызов async не будет блокировать текущее выполнение, и раскрывающееся меню будет скрыто ожидаемо:

private void fileToolStripMenuItem_DropDownItemClicked(object sender, ToolStripItemClickedEventArgs e){
  if (e.ClickedItem.Name == "openToolStripMenuItem")
  {
    BeginInvoke((Action)(()=>{
        if (openFileDialog1.ShowDialog() == System.Windows.Forms.DialogResult.OK)
          MessageBox.Show(openFileDialog1.FileName);
    }));
  }
}

ПРИМЕЧАНИЕ: чтобы скрыть drop down menu вручную в DropDownItemClicked обработчике событий, вы можете использовать e.ClickedItem.Owner.Hide() вместо FileToolStripMenuItem.HideDropDown().

person King King    schedule 28.09.2013
comment
Спасибо. +1 за Owner вещь. Я посмотрел вокруг, но вместо этого искал Parent. Однако асинхронный способ сделать это не нравится. Это полностью пользовательский интерфейс, а не фоновая операция, почему я должен поместить его в другой поток? В общем, элементы фреймворка вызывают события ПОСЛЕ выполнения своего внутреннего управления состоянием, не так ли? например кнопка рисует себя в отпущенном состоянии и только после этого вызывает событие Click. Итак, я не понимаю, для какой цели это служит? Никто и никогда не захочет отображать элемент меню при выполнении каких-либо действий, будь то пользовательский интерфейс или фон. - person dotNET; 28.09.2013
comment
@dotNET BeginInvoke на самом деле не является потоком, он легковесен и часто используется для вызова методов asynchronously, вам нужно много использовать его при программировании на winforms (из-за такой проблемы, как ваша). Я согласен, что такая реализация немного странная. Может поэтому они поддерживают какой-то метод Hide и свойство Visible для выпадающего меню? - person King King; 28.09.2013
comment
@dotNET Я думаю, что здесь есть причина, связанная с side effect, когда Click действительно происходит, DropDownItemClicked должен быть запущен как можно скорее без какой-либо возможности для выполнения какого-либо кода в середине. Предположим, если вы сначала скроете раскрывающееся меню, мы сможем внедрить выполнение некоторого кода в середину перед запуском события DropDownItemClicked, и это даже хуже, чем текущая реализация. По крайней мере, с текущей реализацией мы можем легко исправить так называемую ошибку (двумя указанными мною решениями). - person King King; 28.09.2013
comment
Кстати, я уже упоминал о injecting some code execution in the middle выше, один из очень популярных способов внедрения кода здесь - это добавление обработчика событий для события VisibleChanged, например fileToolStripMenuItem.VisibleChanged += (s,e) => { //much code here...//};. Если это произойдет, much code here будет выполнен первым до того, как будет запущено событие DropDownItemClicked. В этом случае вы можете увидеть некоторое delay между фактическим Click и Clicked событием. - person King King; 28.09.2013
comment
ОК, убедился. Вид... ;) - person dotNET; 29.09.2013