Как стать автором
Обновить

C# WPF аналог Window.ShowDialog() или разбираемся с DispatcherFrame

Время на прочтение2 мин
Количество просмотров6.8K

Постановка задачи


В рамках разработки одного приложения потребовалось реализовать такую схему:


  1. Асинхронный метод запрашивает данные
  2. Пользователь вводит данные с клавиатуры
  3. Метод получает результат ввода как результат выполнения функции и продолжает с того же места

Дополнительные требование: Не создавать дополнительных окон.


Казалось бы, просто? Как оказалось, действительно просто. Но обо всём по порядку.


Решение


Первая попытка сделать это в лоб и без поиска в интернете привела к блокировке основного потока, и, следовательно, ни к чему хорошему. И я уже собирался использовать ShowDialog, как наткнулся на статью. Автор заглянул в то, как сделан ShowDialog в WPF. То, что нужно!


В своей статье он предлагает создать собственную имплементацию метода ShowDialog


[DllImport("user32")]
internal static extern bool EnableWindow(IntPtr hwnd, bool bEnable);

public void ShowModal()
{
    IntPtr handle = (new WindowInteropHelper(Application.Current.MainWindow)).Handle;
    EnableWindow(handle, false);

    DispatcherFrame frame = new DispatcherFrame();

    this.Closed += delegate
    {
        EnableWindow(handle, true);
        frame.Continue = false;
    };

    Show();
    Dispatcher.PushFrame(frame);
}

Мне же не требуется блокировка окна, так как всё показывается в одном окне, а так же требуется возвращаемое значение. Убираем немного лишнего, добавляем нужное...


        public string GetInput()
        {
            var frame = new DispatcherFrame();

            ButtonClicked += () => { frame.Continue = false; };

            Dispatcher.PushFrame(frame);
            return Text;
        }

Dispatcher.PushFrame(frame) предотвращает выход из метода GetInput() до тех пор, пока frame.Continue не станет false. Когда новый фрейм запушен, главный цикл приостанавливается и запускается новый. Этот цикл обрабатывает системные сообщения, в то время как точка выполнения в главном цикле не движется дальше. Когда мы выходим из текущего фрейма (frame.Continue = false), главный цикл продолжает работу с того же места.


Теперь осталось лишь проверить работоспособность.


В MainWindow создадим кнопку и повесим на нее обработчик, который запустит таск, в котором мы и обратимся к вводу с клавиатуры.


Код обработчика:


 public RelayCommand ButtonClick => new RelayCommand(() =>
        {
            Task.Factory.StartNew(() =>
            {
                // имитация работы
                Thread.Sleep(1000);

                // создадим контрол-обработчик ввода
                var control = new PopupControlModel();

                // вызов метода, который останавливает выполнение главного цикла
                Result = control.GetInput();

                // имитация дальнейшей работы
                Thread.Sleep(2000);
            });
        });
    }

Я использовал это решение для ввода пользователем капчи и дополнительного кода при двухфакторной авторизации. Но применений может быть огромное количество.


! В коде примера содержатся нарушения принципа mvvm, и не бейте сильно отсутствует дизайн


Исходный код на github: Proof of concept


Полезные ссылки


Статья "Кастомный ShowDialog"
Скудное описание класса DispatcherFrame с применением машинного перевода
Ожидание завершения через await приведено в этой статье

Теги:
Хабы:
+11
Комментарии3

Публикации

Истории

Работа

Ближайшие события

Weekend Offer в AliExpress
Дата20 – 21 апреля
Время10:00 – 20:00
Место
Онлайн
Конференция «Я.Железо»
Дата18 мая
Время14:00 – 23:59
Место
МоскваОнлайн