Using Fibers for tab-modal forms

By | December 14, 2010

Maybe some don’t know that I’m not only writing IDE plugins but also write code for real world problems. One of those real world problems was that I needed a way to have multiple windows in a “tab-modal” state. If the user switches to another tabsheet, he can open another tab-modal form without closing the one on the other tabsheet. The main issue you have to face is how you get the “ShowTabModal” method to block the execution until the tab-modal form is closed, especially because there can be more than one blocking “ShowTabModal” call.

The naive way would be to call ShowTabModal which has its own message loop like ShowModal. But what if the user closes the parallel opened tab-modal forms in a different order than he opened them? He can’t close the first tab-modal form until he closed the second tab-modal form because the second message loop doesn’t return until the second form is closed.
The first idea to solve this was to use threads for each tabsheet. But that doesn’t work because the VCL isn’t thread-safe and windows have a thread affinity. OK, I got this working by changing the VCL. But with all the global variables in 3rd party controls it is too risky for production usage. The next idea was to follow Google Chrome’s way by using one process per tab. But with the time constraint that I have this can’t be done because I would have to make major changes to our data layer and set up a communication channel between the processes. Some tests also showed me that this is a debugging-nightmare in Delphi (Debug into spawned processes), and that Avira AntiVir would mark my EXE file as a false-positive unless I remove the MainIcon resource.

With all the thinking about how I could solve this issue, I remembered that Windows has something called Fibers. The idea is to execute the message loop in a Fiber making the ShowTabModal call blocking until the SwitchToFiber call returns. This message loop looks for the ModalResult value of all active tab-modal forms and if one is closed it returns to the Fiber that called ShowTabModal for this specific form and deletes the message loop Fiber that was active before returning because it isn’t needed anymore. That way there are n+1 message loops for n ShowTabModal calls (one for the main message loop and n for n ShowTabModal calls) but only one Fiber is active.
This works really well. But then I came to the problem of closing the top level window that hosts all tab-modal forms. If the users presses the window’s X button all tab-modal forms must be closed after consulting their OnCloseQuery event handler. But when closing a form the execution pointer must return to the ShowTabModal call in order to keep the execution flow correct. This is a problem because the code has to wait in the top level window’s OnCloseQuery handler until all tab-modal forms are closed, otherwise the CanClose flag can’t be set correctly. The solution to this was easier than I thought because I just had to do the same thing that I did in ShowTabModal. I had to create a new message loop Fiber that waited until all tab-modal forms are closed or one of them aborted the close. And because all message loop Fibers had to do the job for all forms I put that code into the message loop Fiber procedure that I already had written for ShowTabModal.
The theory and paper drafts of how this all works looked right. But when I closed the top level window I got an access violation in the code the followed the SwitchToFiber call in the top level window’s CloseQuery handler. After some investigation I found out that the stack of the Fiber that ran the CloseQuery handler was taken over by another message loop Fiber. And when the program switched back to the CloseQuery handler it got a trashed stack. But I didn’t see what I was doing wrong, especially if I only opened one level of tab-modal forms, it worked but if I opened two levels (tab-modal forms opens another tab-modal form) it crashed. And if I closed the top level window by using  File/Quit, it worked. So only the system menu, Alt+F4 and the X button crashed the application.
What do those three ways of closing a form have in common? They all come through the WM_SYSCOMMAND->SC_CLOSE message into your program. I overrode the WM_SYSCOMMAND handler and when it is a SC_CLOSE command I called PostMessage(WM_CLOSE) instead of inherited. This trick solved the crash. So there must be something really weird going on in the WM_SYSCOMMAND message handler that makes Fibers suddenly use the stack of another Fiber.

12 thoughts on “Using Fibers for tab-modal forms

  1. Cameron

    We do it by glassing the panel or tab itself then using show instead of showmodal. Very simple design and handles all of our needs. Easy to do by creating an empty form that consumes the size of the tab. With AlphaBlend it even looks very nice.

  2. Mitja

    Will you be posting any samples? This sounds really interesting.

  3. Luciano Guimaraes

    Really nice article.
    I’ve done this using Show and Observer design Pattern. But this means changes on every ShowModal call. I’m very impressed with your solution, could you post a code sample ?

  4. Xepol

    A sample would be intersting on multiple levels. First for the problem itself, and then on the fibre.

    I would encourage you to continue with the Google solution, but I suspect you’d find the VCL would still make it far too difficult. (the processes would have to render of offscreen bitmaps, mouse messages etc would all have to be simulated across process boundaries, third party controls would probably still act funny when you did not send enough of the right messages for some of them). Probably a dead end – tho if anyone solves it in the VCL delphi world, it would be a huge boon for many of us that work on integrating many discrete application parts into a single greater gestalt.

    1. Andreas Hausladen Post author

      There is no need to render the controls off-screen. You can use Windows.SetParent() to “move” the windows of the other processes into your main form.

      Here is a little proof of concept project that I wrote some weeks ago. It shows how a tabbed application with one process per tab could work. I don’t give support and I don’t answer any question about this test project.
      https://www.idefixpack.de/misc/ProcessWindows.7z

      1. Xepol

        It doesn’t work Andy. What you do ties all the UI into a single message queue hosted by the host app – effectively it all runs in the host app’s thread. Just add a button to your hosted UI that does a sleep(10000) – you will see the entire UI freeze.

        As I understand Chrome’s solution- you create an offscreen UI, the host app can then grab it, render it as required and feed back fake mouse and keyboard inputs. When a hosted APP freezes, the main UI app can detected it and throw away the process without dying itself.

        1. Andreas Hausladen Post author

          > It doesn’t work Andy

          Thanks for the hint. I only had some minutes to write that code and I was more interested in getting the debugger working in a way that somebody can debug the code (what the Delphi-Debugger can’t handle well).

Comments are closed.