Sunday, April 3, 2011

Custom ShowDialog() method

In my first article I have considered DispatcherFrame. I have tried to look in-depth of DispatcherFrame, showed some IL sources of .Net and tried to explain what it is.
While reading MSDN about threading model, I met a section, which said that PushFrame() can be used to create something like WPF dialog windows. That windows take control after calling ShowDialog() and doesn’t return control to parent window until closed. So, I wondered, how to create such window?

Custom ShowModal()

For the first, I tried to open new window in separate thread. It works! But when I clicked on suspended parent window for 5-10 times, that window get “Not responded”. Not good!
I was really wondered how to block parent window, but prevent it from suspension. I thought about how to use DispatcherFrame here. Obviously, if main thread is blocked, then parent window will be also blocked, because of blocked dispatcher. So, there is no way to use PushFrame in such solution.
After that I decided to use Reflector to see how ShowDialog() is implemented in WPF. So, what I see? WINAPI used there to block each window in current thread.

<span lang="EN-US"></span>[SuppressUnmanagedCodeSecurity, 
SecurityCritical, DllImport("user32.dll", EntryPoint="EnableWindow", CharSet=CharSet.Auto)] 
public static extern bool IntEnableWindowNoThrow(HandleRef hWnd, bool enable); 
This method is called for each window with enable = false. As you guess solution becomes very closer. To implement my own ShowModal() I should perform following steps:
- Block parent window
- Add event handler on modal closed and unblock parent window inside this handler
- Show modal window and call PushFrame()
That’s it!

[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);
        }
 
First, we define extern method which uses WINAPI to enable/disable windows. Exactly that method used in WPF ShowDialog(). Than we blocks window, show modal and push new DispatcherFrame.
Why we have to use PushFrame() here? Answer is to prevent returning from ShowModal() method. When new frame is pushed, main loop suspended and new loop starts. This new loop processes system messages and execution point in main loop doesn’t go forward. When modal closed, DispatcherFrame.Continue = false, and this frame is exited. And after that application back to normal flow
This approach has no any obvious advantages in comparing with ShowDialog() method. Dialog behaviour is totally the same.

The one benefit, that is provided by this way is full control on blocking and unblocking windows from modal window. Say, what if u need to block only 2 of 4 active windows? Or unblock window while dialog is showed? It could be problematically with classic ShowDialog() method. Ofcourse, you can use WinAPI, but this is not so obviously as with custom ShowModal() method.

Also, you can put windows blocking and unblocking command anywhere. Just put PushFrame() and frame.Continue = false, to places of code, where you wanna stop or continue default application flow, and you will get it. It can be usefull if you want to continue main frame flow without closing of modal window or otherwise dont continue it after closing.

No comments:

Post a Comment