(Print this page)

Hosting an application in a Windows Forms
Published date: Monday, January 7, 2013
On: Moer and Éric Moreau's web site

Every so often, I have a request to host an application like Word or Excel in a Windows forms for whatever reason. You can’t always argue with clients!

This article will try to do that. You will find that it is very easy for some application while it is a pain for others (like Excel).

Downloadable demo

This month, the code is provided in both VB and C#. The solution has been created with Visual Studio 2012 but you can probably reused the code in any .Net version because the code is not relying on specific features.

A bit of PInvoke

Some of the features used here requires features that are purely Windows and have nothing to do with .Net. RedGate’s PInvoke wiki has been very helpful to find the correct signatures for both VB and C#.

Building the UI

If you look at figure 1, you can find almost all controls in use for this demo application. Starting top-left, you can see a Label (with its Text property set to Process), a TextBox control (named txtProcess currently disable by the code) and 2 buttons (named btnStart and btnStop). The control that is not really visible a Panel control used to host the Process we will launch. This Panel has been anchored to the 4 sides of the form to ensure it grows and shrinks when the form is resized.

Figure 1: The demo application in action

Declaring external constants and functions

Before we start writing code, we need to declare some Win32 constants and functions found in the user32.dll.

Here is this code:

Private Const SC_MAXIMIZE As Integer = 61488
Private Const WM_CLOSE As Integer = 16
Private Const WM_SYSCOMMAND As Integer = 274

Declare Auto Function IsWindowVisible Lib "user32.dll" (
                                                       ByVal hWnd As IntPtr
                                                       ) As Boolean
Declare Auto Function MoveWindow Lib "user32.dll" (
                                                  ByVal hWnd As IntPtr,
                                                  ByVal x As Integer,
                                                  ByVal y As Integer,
                                                  ByVal nWidth As Integer,
                                                  ByVal nHeight As Integer,
                                                  ByVal bRepaint As Boolean
                                                  ) As Boolean
Declare Auto Function PostMessage Lib "user32.dll" (
                                                   ByVal hWnd As IntPtr,
                                                   ByVal msg As UInteger,
                                                   ByVal wParam As IntPtr,
                                                   ByVal lParam As IntPtr
                                                   ) As Boolean
Declare Auto Function SendMessage Lib "user32.dll" (
                                                   ByVal hWnd As IntPtr,
                                                   ByVal msg As Integer,
                                                   ByVal wParam As Integer,
                                                   ByVal lParam As Integer
                                                   ) As Integer
Declare Auto Function SetParent Lib "user32.dll" (
                                                 ByVal hWndChild As IntPtr,
                                                 ByVal hWndNewParent As IntPtr
                                                 ) As Integer

Starting a Process

Now that we have the Windows’ functions declared, we can start writing some code.

The first snippet we can write is the code behind the Start button. The first task we have to do in this section is trying to start the process itself (the one we have in the Textbox). This process is started using the Process Component (which has been covered by an article in 2003). Because we are using the Process component, we can try to start any process like Notepad.exe which is in one of the known paths of Windows. You can also try to start a file like shown in figure 1 which will automatically launch the associated application with this file opened.

If the process is correctly started, we retrieve its handle (using the MainWindowHandle property). With this handle, we force he parent to be our Panel control on our form and finally send a command to maximize the application to fit the Panel size.

Here is the code for this part:

Private _appHandle As IntPtr

Private Sub btnStart_Click(sender As Object, e As EventArgs) Handles btnStart.Click
    Dim proc As Process
    'tries to start the process 
    Try
        proc = Process.Start(txtProcess.Text)
    Catch ex As Exception
        MessageBox.Show("Something went wrong trying to start your process",
                        "App Hoster",
                        MessageBoxButtons.OK,
                        MessageBoxIcon.Error)
        Return
    End Try

    'disables button and textbox
    txtProcess.Enabled = False
    btnStart.Enabled = False

    'host the started process in the panel 
    Threading.Thread.Sleep(500)
    Do While (proc.MainWindowHandle = IntPtr.Zero OrElse Not IsWindowVisible(proc.MainWindowHandle))
        Threading.Thread.Sleep(10)
        proc.Refresh()
    Loop

    proc.WaitForInputIdle()
    _appHandle = proc.MainWindowHandle

    SetParent(_appHandle, panel1.Handle)
    SendMessage(_appHandle, WM_SYSCOMMAND, SC_MAXIMIZE, 0)

Resizing the Process

This feature is really easy to implement.

What we want to do here is to resize the hosted application to follow the size of the Panel (remember that we have anchored the Panel to follow the size of the form).

Here is the code you will find in the Panel’s Resize event:

If _appHandle <> IntPtr.Zero Then
    MoveWindow(_appHandle, 0, 0, panel1.Width, panel1.Height, True)
End If

Closing a Process

The Stop button is used to dispose the hosted application. Because we need to also dispose the application when closing the form, I have created a method called CloseProcess. All it does is to send a message to the process to kill itself. We can’t do really much more because we don’t know which application we are hosting.

Here is the code:

Private Sub CloseProcess()
    'Close the running process
    If _appHandle <> IntPtr.Zero Then
        PostMessage(_appHandle, WM_CLOSE, IntPtr.Zero, IntPtr.Zero)
        Threading.Thread.Sleep(1000)
        _appHandle = IntPtr.Zero
    End If
    'enables button and textbox
    txtProcess.Enabled = True
    btnStart.Enabled = True
End Sub

The problems

I found some problems with this implementation.

The first problem is that you will see the application you want to host outside your panel for a short period. The reason is that the application has to be started before it can be hosted into your own application. But I can leave with it.

Second, some applications like the overly used Excel does not seem to be a good citizen. Look at figure 1 again. Anything is missing from my excel 2013? Where is the ribbon? I would have like a menu bar but none of them is showing. How can one really use Excel now? Other Office applications behaves correctly, Word is one of them.

You will also have another problem if for example Word is already running and you try to host it in your Panel. Using this code, you won’t be able to grab the process handle and you won’t be able to host it.

Also, when stopping a process, if the process tries to send a message to the user (like the famous “do you want to save”). Sometimes (most of the time with Excel), this message box is shown in the background somewhere. All you will see is the Excel title bar flashing and you need to click on it to get access to the message box.

Conclusion

I would have been a liar telling you that this solution is the answer to all your application hosting needs. As I said earlier, it is not an ideal solution in all scenarios (mostly with Office applications).

I just enumerated a couple (but importation) limitations here but I also found applications working great.

If you want to host your own application and if your application is simple and standard, it might be a good candidate for hosting.

You really need to test with your own environments to see if you have a solution here!


(Print this page)