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:
- Asynchronous method requests data
- The user enters data from the keyboard
- 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.Continue
it 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.