C # WPF analogue Window.ShowDialog () or deal with DispatcherFrame

Formulation of the problem


As part of the development of one application, it was required to implement the following scheme:


  1. Asynchronous method requests data
  2. The user enters data from the keyboard
  3. The method receives the input result as the result of the function and continues from the same place

Additional requirement: Do not create additional windows.


It would seem simple? It turned out to be really simple. But first things first.


Decision


The first attempt to do it head-on and without searching the Internet led to a blockage of the main stream, and, therefore, to no good. And I was about to use ShowDialog, as I came across an article . The author looked at how ShowDialog is made in WPF. Exactly what is needed!


In his article, he suggests creating his own implementation of the ShowDialog method


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

I do not need a window lock, since everything is shown in one window, and a return value is also required. We remove a little too much, add the right one ...


        public string GetInput()
        {
            var frame = new DispatcherFrame();
            ButtonClicked += () => { frame.Continue = false; };
            Dispatcher.PushFrame(frame);
            return Text;
        }

Dispatcher.PushFrame(frame)prevents exit from the method GetInput()until frame.Continueit becomes false. When a new frame is started, the main loop pauses and a new one starts. This loop processes system messages, while the execution point in the main loop does not move further. When we exit the current frame ( frame.Continue = false), the main loop continues to work from the same place.


Now it remains only to check the performance.


In MainWindow, create a button and put a handler on it that will launch the task, in which we will turn to keyboard input.


Handler Code:


 public RelayCommand ButtonClick => new RelayCommand(() =>
        {
            Task.Factory.StartNew(() =>
            {
                // имитация работы
                Thread.Sleep(1000);
                // создадим контрол-обработчик ввода
                var control = new PopupControlModel();
                // вызов метода, который останавливает выполнение главного цикла
                Result = control.GetInput();
                // имитация дальнейшей работы
                Thread.Sleep(2000);
            });
        });
    }

I used this solution to enter captcha and additional code for two-factor authentication. But there can be a huge number of applications.


! The sample code contains violations of the mvvm principle, anddon't hit hard missing design


Github source code: Proof of concept


useful links


Custom ShowDialog
article A scant description of the DispatcherFrame class using machine translation
Waiting for completion via await is given in this article.


Also popular now: