Introduction
Sometimes you might have wondered how Chrome browser creates different tabs as different process and how they add into a tab. This article is all about creating a Tabbed multi process application.
Multi process architecture is a known concept to all and has been in use for the past several years, for example, Google chrome, a popular browser where each tab is a separate process. How do they do it ?
Few months ago, a similar requirement was raised in our team too, which prompted me to use such a design. This application helps client in easy management of different networks and also easy identification of user interface as the application is tabbed.
This article is all about how each tab could host different application processes.
Background
Starting from the very basics, in Windows, each UI object has a unique value called handle (refer to the below link). A handle is a pointer and in C#, for every UI object, there is property named Handle
using which we could retrieve its Handle
. Using this handle and using Windows API, we can easily embed an application into a Windows UI component.
Now let us make our hands wet....
Start Visual Studio and create a solution with two Windows applications in it, one being Child and another being Parent - both should be in the same platform (You could use any existing application as a child application like WordPad, I would suggest to target x86 platform initially if using a legacy application like Notepad).
In the parent application, add a ‘Table layout panel’ control and configure it with two columns.
In the left column – you could add a treeview / buttons as per your choice for launching the application.
In the right column, add a tab panel and set the Dock
Property to Fill
.
In the parent application, add static
classes named WindowsAPI
and WindowsConstants
. (These classes will help in calling Windows API.)
In the WindowsAPI
class, add namespace reference to “Microsoft.Win32
” and “System.Runtime.InteropServices
” Now, modify the class definition with the following code:
[DllImport("user32.dll",SetLastError = true)]
public static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent);
[DllImport("user32.dll", CharSet =CharSet.Auto)]
[return: MarshalAs(UnmanagedType.Bool)]<span></span>
public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
SetParent
API - Use this to create forms and then place child windows on it.
ShowWindowAPI
- Sets the specified window's show state.
In the class WindowsConstants
, add the following constants:
public static readonly int SW_MAXIMIZE = 3;
Coming back to the Mainform of the parent application, subscribe the click event of the button/treeview; add the following code in the click event handler.
Process process =Process.Start("Wordpad.exe");
process.WaitForInputIdle();
tab.Text = "Wordpad "+ counter;
WindowsAPI.SetParent(process.MainWindowHandle,tab.Handle);
WindowsAPI.ShowWindow(process.MainWindowHandle,WindowsConstant.SW_MAXIMIZE);
This will make the child application (say notepad.exe here) to attach a process to a tab. But the new process contains title bar menubar and border, which makes it easy to close the application. When we are in control of both child and parent application, we can easily get over this problem by changing the Form properties (BorderStyle
). But how can we control the menu and border properties of an application which is out of our control (Say the case of wordpad.exe)? Like SetParent
, there are Windows API for changing the Windows border of any application.
Modify the WindowsAPI
class to accommodate new WindowsAPI
.
public static IntPtr GetWindowLongPtr(HandleRef hWnd, int nIndex)
{
if (IntPtr.Size == 8)
return GetWindowLongPtr64(hWnd,nIndex);
else
return GetWindowLong32(hWnd, nIndex);
}
[DllImport("user32.dll", EntryPoint= "GetWindowLong")]
private static extern IntPtr GetWindowLong32(HandleRef hWnd, int nIndex);
[DllImport("user32.dll", EntryPoint= "GetWindowLongPtr")]
private static extern IntPtr GetWindowLongPtr64(HandleRef hWnd, int nIndex);
public static IntPtrSetWindowLongPtr(HandleRef hWnd, int nIndex, IntPtr dwNewLong)
{
if (IntPtr.Size == 8)
return SetWindowLongPtr64(hWnd,nIndex, dwNewLong);
else
return new IntPtr(SetWindowLong32(hWnd, nIndex, dwNewLong.ToInt32()));
}
[DllImport("user32.dll",EntryPoint = "SetWindowLong")]
private static extern int SetWindowLong32(HandleRef hWnd, int nIndex, int dwNewLong);
[DllImport("user32.dll",EntryPoint = "SetWindowLongPtr")]
private static extern IntPtr SetWindowLongPtr64(HandleRef hWnd, int nIndex, IntPtr dwNewLong);
Now modify the WindowsConstants
class with the following values:
public static readonly int GWL_STYLE = -16;
public static readonly UInt32 WS_CAPTION = 0xC00000;
public static readonly UInt32 WS_BORDER = 0x00800000;
public static readonly UInt32 WS_DLGFRAME = 0x00400000;
Now in the click event handler of the button, modify the code to remove the title bar and form border.
long style =WindowsAPI.GetWindowLongPtr(new HandleRef(this, process.MainWindowHandle),
WindowsConstants.GWL_STYLE).ToInt64();
style= style & ~( WindowsConstants.WS_CAPTION |WindowsConstants.WS_BORDER | WindowsConstants.WS_DLGFRAME);
IntPtr styleValue = new IntPtr(style); WindowsAPI.SetWindowLongPtr(new HandleRef
( this, process. MainWindowHandle), WindowsConstants.GWL_STYLE, styleValue);
This will initially get the window style of the child application’s main window and then remove the caption, border and dialog frame properties then reset the updated style to the mainwindow.
Now this will really help in removing the border and title bar from a window.
But still, we haven’t made the new application the child of the parent application.
We need to do this because, on termination of the parent application, its child application may not all the time get removed from the process queue. This will result in execution of the application in background without our knowledge consuming the processor resources and memory which is a waste.
Now modify the WindowsConstants
class by adding new constants.
public static readonly long WS_CHILD =0x40000000L;
public static readonly long WS_POPUP =0x80000000L;
In the button click event handler, once again set the window style.
style = WindowsAPI.GetWindowLongPtr(new HandleRef(this, tab.Handle), WindowsConstants.GWL_STYLE).ToInt64();
style &= ~ WindowsConstants.WS_POPUP;style|= WindowsConstants.WS_CHILD;
styleValue = new IntPtr(style);
WindowsAPI.SetWindowLongPtr(new HandleRef(this, tab.Handle), WindowsConstants.GWL_STYLE, styleValue);
This will help in removing the child application on closing the parent from process queue.
Finally, the button's click event handler code is as follows:
private void StartWordPadButton_Click(object sender, EventArgs e)
{
Process ProcessInfo = Process.Start("Wordpad.exe");
ProcessInfo.WaitForInputIdle();
TabPage tab = new TabPage();
tab.Text = "Wordpad ";
tabControl1.TabPages.Add(tab);
long style =0;
style = WindowsAPI.GetWindowLongPtr(new HandleRef
(this, ProcessInfo.MainWindowHandle), WindowsConstant.GWL_STYLE).ToInt64();
style = style & ~(WindowsConstant.WS_CAPTION |
WindowsConstant.WS_BORDER |WindowsConstant.WS_DLGFRAME);
IntPtr styleValue = new IntPtr(style);
WindowsAPI.SetWindowLongPtr(new HandleRef(this,
ProcessInfo.MainWindowHandle), WindowsConstant.GWL_STYLE, styleValue);
style = WindowsAPI.GetWindowLongPtr(new HandleRef
(this, ProcessInfo.MainWindowHandle), WindowsConstant.GWL_STYLE).ToInt64();
style &= ~WindowsConstant.WS_POPUP;
style |= WindowsConstant.WS_CHILD;
styleValue = new IntPtr(style);
window is removed.
WindowsAPI.SetWindowLongPtr(new HandleRef(this,
ProcessInfo.MainWindowHandle), WindowsConstant.GWL_STYLE, styleValue);
WindowsAPI.SetParent(ProcessInfo.MainWindowHandle,tab.Handle);
WindowsAPI.ShowWindow(ProcessInfo.MainWindowHandle,WindowsConstant.SW_MAXIMIZE)
}
Your suggestions and feedback will be very helpful to me for my upcoming articles.