Although it is not the most common use of it, it is possible to host a Windows Form Control in a Web Page and run it from within Internet Explorer. This allows to build powerful client side functionality with all the advantages of using the .Net framework and executing the control in the client side. Of course there are some restrictions that cannot be left aside. At least the .Net framework must be installed on the client for the control to run. In addition, it is possible that some permission must be granted to the control, too, depending on the actions the control will take on the client machine.
Let’s build an example to see how this works:
1. Create the Windows Form Control
Create a “Windows Control Library” project in Visual Studio. For this simple example we will add a label to the control and a public “SendMessage” method to change the label’s text. In order to be able to call the method from IE, we will set the control’s COMVisible attribute to true.
using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;
namespace WindowsControlLibrary1
{
[ComVisible(true)]
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
}
public void SendMessage(string msg){
_lblMessage.Text = msg;
}
}
}
2. Create the hosting HTML document
The control will be hosted using the HTML <object> element. In its classid attribute we will place the control's reference in the following manner: classid="http:[relativePath/]<winControlAssemblyName>.dll#<ControlNamespace>.<ControlClassName>". For this example we will have an HTML page (index.html) with the following code:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head>
<title>Windows Form Control Test Page</title>
<script type="text/javascript">
function SendMessageToWinControl()
{
var winCtrl = document.getElementById("MyWinControl");
winCtrl.SendMessage("Message sent from the HTML page!!");
}
</script>
</head>
<body>
<h1>Windows Form Control:</h1>
<object id="MyWinControl" classid="http:WindowsControlLibrary1.dll#WindowsControlLibrary1.UserControl1"
height="100" width="300" VIEWASTEXT/>
<br/><br/>
<input type="button" onclick="SendMessageToWinControl()" value="Send Message" />
</body>
</html>
3. Deploy the sample
Copy both the index.html page and the control library WindowControlLibrary1.dll to a directory (we will name it WinControlTest). In the “Web Sharing” dir properties select Share this folder, leave the default alias and application permissions to Scripts and make sure not to select the Execute permission).
4. Try it in Internet Explorer
Open the page http://localhost/WinControlTest/index.html in Internet Explorer. We can see the control hosted in the html page:

By clicking the Send Message button, we will call the Control's SendMessage method from javscript. Resulting in the label's text changed:
If you modify the control after its first use, you will need to close IE, replace the control's dll in the virtual directory and clean the contents of the download cache. You do this cleaning using the following command: "gacutil /cdl".To list the contents of the download cache use "gacutil /ldl".
This solution is not intended to replace a WebControl or any common web artifact. But is good to know it can be use in an uncommon scenario if needed.
Sole
Windows Presentation Foundation (WPF) provides a very interesting API for Accessibility called Microsoft UI Automation. It allows programmatic access to most user interface elements on the desktop, addressing the needs of assistive technology products and also for User Interface (UI) tests automation.
The framework provides solutions for both accessibility providers and clients, and it is conformed of four main components (see UI Automation Overview):
- The Provider API (UIAutomationProvider.dll and UIAutomationTypes.dll) defines a set of interfaces that are implemented by UI Automation providers, objects that provide information about UI elements and respond to programmatic input.
- The Client API (UIAutomationClient.dll and UIAutomationTypes.dll) is a set of types for managed code that enables UI Automation client applications to obtain information about the UI and to send input to controls.
- The UI Automation Core (UiAutomationCore.dll) has the underlying code that handles communication between providers and clients.
- The UIAutomationClientsideProviders.dll that has a set of UI Automation providers for standard legacy controls. (WPF controls have native support for UI Automation.) This support is automatically available to client applications.
We will typically use the Provider API to create support for our WPF custom controls, since they don't provide accessibility support by default. We will use the Client API for creating applications that need to communicate with UI elements and eventually automate the use of other program's UI.
You can find good documentation in the WPF Accessibility section on MSDN, and several code samples in the SDK Sample Applications.
Just to mention, Microsoft’s earlier solution for Accessibility was Active Accessibility. The main advantage of UI Automation over Active Accessibility is the technology in which it is based on (managed code vs COM) and, as a consequence, the programming languages that can be used. You can read more of the differences in UI Automation and Microsoft Active Accessibility.
A few words about the API's Object Model: Every piece of UI, such as a window, a button, etc, is represented by the AutomationElement class in the System.Windows.Automation namespace of the UIAutomationClient assembly. An AutomationElement corresponds to a piece of UI regardless of the underlying implementation (WPF or Win32). All automation elements are part of a tree, in which the root element is the Desktop. Through the AutomationElement.RootElement static property you can obtain a reference to the Desktop and find any child piece of UI from there to access.
AutomationElements expose Control Patterns that provide properties specific to their Control Types (window, button, checkbox, etc). Control Patterns also expose methods that enable clients to get further information about the element and to provide input. We are going to see how this works in the examples below.
A very useful tool for finding the element's properties in the tree is UISpy, which comes with the Windows SDK. It lets you see all the UI tree and each element's properties. In the Focus Tracking mode, you can select the real UI element in the Desktop and UISpy will automatically show its properties in the tree view.
Code sample
This is an example of a client application for automating a win32 application (in this case the Windows Address Book). For this example, we only need a Windows Console Application and references to the UIAutomationClient and UIAutomationTypes assemblies. From the code it can be seen how to match a control to a given pattern depending on what we want to do with the control.
The controls' names and/or ids where obtained using UISpy.
These are the UI artifacts that we want to automate:
And here is the code to:
- Get a reference to the Address Book application main window:
using System.Windows.Automation;
...
AutomationElement mainWindow = FindWindowByName(AutomationElement.RootElement, "Address Book - Main Identity");
...
private static AutomationElement FindWindowByName(AutomationElement rootElement, string name)
{
PropertyCondition nameCondition = new PropertyCondition(AutomationElement.NameProperty, name);
PropertyCondition typeCondition = new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Window);
AndCondition andCondition = new AndCondition(nameCondition,typeCondition);
return rootElement.FindFirst(TreeScope.Element | TreeScope.Descendants, andCondition);
}
- Press the Find People button:
AutomationElement findPeopleButton = FindElementById(mainWindow, "Item 8084");
if (findPeopleButton != null)
{
InvokePattern invPattern = findPeopleButton.GetCurrentPattern(InvokePattern.Pattern) as InvokePattern;
if (invPattern != null)
{
invPattern.Invoke();
}
}
...
private static AutomationElement FindElementById(AutomationElement parentElement, string automationID)
{
PropertyCondition propertyCondition = new PropertyCondition( AutomationElement.AutomationIdProperty, automationID);
return parentElement.FindFirst(TreeScope.Element | TreeScope.Descendants, propertyCondition);
}
- Attach to the Find People dialog Closed event:
AutomationElement dialog = FindWindowByName(AutomationElement.RootElement, "Find People");
if (dialog != null)
{
Automation.AddAutomationEventHandler(WindowPattern.WindowClosedEvent, dialog, TreeScope.Element,
dialogEventHandler = new AutomationEventHandler(OnWindowClosedEvent));
}
...
private static void OnWindowClosedEvent(object src, AutomationEventArgs e)
{
Console.WriteLine("On window closed event");
}
- Attach to the Dialog's Close Button Clicked event:
AutomationElement closeButton = FindElementById(dialog, "68");
if (closeButton != null)
{
Automation.AddAutomationEventHandler(InvokePattern.InvokedEvent,closeButton, TreeScope.Element,
clickedEventHandler = new AutomationEventHandler(OnCloseButtonClickedEvent));
}
...
private static void OnCloseButtonClickedEvent(object src, AutomationEventArgs e)
{
Console.WriteLine("On button clicked event");
}
- Get and set the value of the Name text box of the Find People dialog:
AutomationElement text = FindElementById(mainWindow, "1152"); //Automation Id = 1152 of Name text box find with UISpy
if (text != null)
{
//get content
TextPattern txtPattern = text.GetCurrentPattern(TextPattern.Pattern) as TextPattern;
if (txtPattern != null)
{
string content = txtPattern.DocumentRange.GetText(-1);
Console.Out.WriteLine(content);
}
//write content
ValuePattern valuePattern = text.GetCurrentPattern(ValuePattern.Pattern) as ValuePattern;
valuePattern.SetValue("new value");
}
- Set a combo box value:
AutomationElement combo = FindElementById(dialog, "99");
if (combo != null)
{
AutomationElement item = FindListItemByName(combo,"Active Directory");
SelectionItemPattern itemPattern = item.GetCurrentPattern(SelectionItemPattern.Pattern) as SelectionItemPattern;
itemPattern.Select();
}
Well...that's it. I hope this helps for a start. Enjoy!
Sole