Introduction
It’s no secret that all the cool action is happening on the web! – It seems that every other week, a new JavaScript/HTML5 framework is born. With HTML5, the browser itself includes some great toys in the box. It is amazing what you can achieve using CSS alone these days. Factor in canvas support and WebGL and the browser is where it’s at!
Wouldn’t it be nice to harness and include some of that functionality in our WinForms application?
I have noticed more and more traditional Windows desktop applications where the UI has a HTML feel to them. Take, for example, the screenshot below of the TeamViewer
application. Now granted, I'm not suggesting it’s a C#/WinForms application but the user-interface borrows heavily from HTML.

Traditionally, of course WinForms has always come bundled with a WebBrowser control but typically this was subpar in that it didn’t supply the latest features and you was at the mercy of the version of Internet Explorer that was installed on the client machine.
The source code for this project can be found on GitHub at:
Goals
My goals for this project are to create a simple proof of concept C# WinForms application that can leverage HTML as a User Interface. From this, we should be able to have the ability to:
- display HTML on a WinForms Application
- call a JavaScript function from C#
- call a C# function from JavaScript
- pass data between C# and JavaScript in either direction
- debug HTML/JavaScript using Chrome Dev Tools
Chromium and the Chromium Embedded Framework
To meet the goals above, I will be using the open source Chromium web browser and in particular the Chromium Embedded Framework (CEF). From their website, the Chromium Embedded Framework (CEF) is a simple framework for embedding Chromium-based browsers in other applications.
Honourable Mentions
Whilst researching embedding HTML content in a .NET app, I came across other projects which deserve a mention.
Name
|
Comment
|
Where To Find It
|
Inbuilt C# Web Browser Control
|
This control actually can actually make calls through to JavaScript and vice-versa. The problem with this control is that you are tied to the version of Internet Explorer that is installed on the host OS and there is no runtime debugging feature.
|
https://msdn.microsoft.com/en-us/library/system.windows.forms.webbrowser%28v=vs.110%29.aspx
|
HTML Renderer
|
This is a fantastic little framework. It is lightweight and includes only 2 .DLL's. However, I was unable to see if you can call JavaScript or if JavaScript can call back to C# code ( I don’t believe it includes a JS engine )
|
https://htmlrenderer.codeplex.com/
|
Awesomium HTML UI Engine
|
This looks very good with its own API. However I excluded it as it looked like it is an API on top of Chromium. For this article, I decided to investigate Chromium and the CEF framework.
|
http://www.awesomium.com/
|
GeckoFX
|
GeckoFX uses the Firefox engine. This also looks good. Like Chromium, it is open-source and a full modern implementation of a browser.
|
https://bitbucket.org/geckofx
|
Should We Do This?
This is a code-journey I wanted to make to see what can be accomplished using the Chromium project. The Jurassic park line: "we were so pre-occupied with whether we could, we didn't stop to think whether we should" applies here. You need to decide for yourself what’s right for you and what’s right for your project. - For me, I think this stuff is cool!!
Advantages of Using Chromium / CEF
The reason why I chose Chromium over the other browser engines is that first and foremost, it is a fully modern browser and when used in conjunction with CEF allows you to implement HTML5 based user interfaces with ease in a WinForms application. Chromium itself supports all the latest goodies we come to expect from a modern HTML5 browser.
- HTML5
- CSS3
- Canvas
- SVG
- WebGL
- Dev Tools
To see how feature complete Chromium is with regards to HTML5 and CSS3 features, we can visit:
But the main reason for choosing Chromium is that interactive between JavaScript and C# code is easy. We can call C# code from JavaScript land and vice-versa with ease.
Finally, the big feature is that Chromium includes the full debugging dev-tools shipped with Chrome. We are going to be writing JavaScript code or including JavaScript libraries with our application and the ability to debug them in-app is essential. This puts Chromium over the top.
Drawbacks of Using Chromium
Okay, so what are the drawbacks of using Chromium? Chromium is by no means lightweight. It depends on a number of DLLs that you will need to bundle with your application. But for me, this is a small price to pay for including such great abilities.
Setting Up Chromium in a WinForms Project
To include chromium in your WinForms project, please refer to the article series "Display HTML in WPF and CefSharp" by Dirkster99 and Alex Maitland. These articles will get you up and running.
You can install Cefsharp into your solution with ease by using Nuget.
Example of using Nuget to install CefSharp in your WinForms project.

Once you have got CefSharp installed, you will notice this warning:

Again, referring to Dirkster99 and Alex Maitland above articles, we need to specify the platform we are targeting. I will choose x86.
Example of setting up platform builds x86 and x64.

Once these have been set, the project will build successfully.
At this point, our project includes all the functionality needed to use Chromium. Let’s now take a look at how we use this.
A Quick Tour Around the Chromium API
Adding the Chromium Web Browser Control
To add the Chromium Web Browser to the Form, simply include the following code into the Forms Load()
and FormClosing()
methods:
Code Snippet 1: Getting started with Chromium |
private void Form1_Load(object sender, EventArgs e)
{
Cef.Initialize();
ChromiumWebBrowser myBrowser = new ChromiumWebBrowser("http://www.maps.google.com" );
this.Controls.Add(myBrowser);
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
Cef.Shutdown();
}
|
Note: The function calls Cef.Initialize();
and Cef.Shutdown();
only need to be called once in the application.
This will add the Chromium browser control to the main form. In the above code, we are setting the default URL to http://www.maps.google.com. This is what our resulting form looks like:
Screenshot of Sample Application.

Now, let’s explore what we can do with this:
Displaying the Chromium Dev Tools
Displaying the dev tools for a page is as easy as a simple function call.
Code Snippet 2: Displaying the Chromium Dev Tools |
private void buttonShowDevTools_Click(object sender, EventArgs e)
{
m_chromeBrowser.ShowDevTools();
}
|
This will bring up the familiar chrome developer tools. You get access to everything and allows you to inspect the DOM and elements, debugging JavaScript code, viewing CSS styles, run commands via the console, etc.
Screenshot of Chrome DevTools

Web developers are used to viewing the dev tools to inspect/modify code by pressing F12 key. I have added a menu option to the left system menu (see Fig 3) that allows you to view the chrome dev tools at runtime. To insert the menu option, I have created a utility class that injects the menu option.
[As a side-note, I initially attempted to catch the F12 key on the form, however even with KeyPreview
set to true
, it seemed Chrome caught all key strokes and the key preview was never caught by the WinForm
class.]
Screenshot of Chrome Dev Tools Menu option

To enable this menu, you make a call to the following static
class in the forms Load
method:
ChromeDevToolsSystemMenu.CreateSysMenu(this);
Then override the WndProc
method listening for the SYSMENU_CHROME_DEV_TOOLS
menu selection.
Code Snippet 3: Example of loading custom HTML |
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
if ((m.Msg == ChromeDevToolsSystemMenu.WM_SYSCOMMAND) &&
((int )m.WParam == ChromeDevToolsSystemMenu.SYSMENU_CHROME_DEV_TOOLS))
{
m_chromeBrowser.ShowDevTools();
}
}
|
The code for this ChromeDevToolsSystemMenu
class is included in the sample project. It is not described here as it detours from the main premise of the article.
Finding Out Which Version of Chromium You Are Using
To find out what version of chrome you are using within your application, simply navigate to this URL:
chrome://version/
This will tell you which version of chromium you are running in addition to the version of CEF.
Screenshot of chrome version

In my instance, I am running version 39 which was released around August 2014.
Displaying "on the fly" HTML in Chromium
Our main purpose for including Chromium in a WinForms app is to display custom HTML. To do this, you need to call LoadHtml()
. This function expects:
Code Snippet 4: Example of loading custom HTML |
private void buttonCustomHTML_Click(object sender, EventArgs e)
{
m_chromeBrowser.LoadHtml( "Hello world" , "http://customrendering/" );
}
|
Including HTML Assets in a Visual Studio Project
I am going to organise the HTML resources/assets in the following directory structure. You can create a directory structure that suits you.
Screenshot of directory structure

You can either create these directories directly In Visual Studio's Solution Explorer, however I find it easier to create your directory structure directly in Windows Explorer.
Then, in Visual Studio, click the "Show All Files" icon in Solution Explorer (shown below). After that, the added directories will show up in Solution Explorer. You will then need to select this directory, right click, and choose "Include in Project."
Screenshot of “Show All Files” button in Solution Explorer.

Screenshot of “Include In Project” right-click context menu.

Finally, in Visual Studio, make sure the "Build Action" property is set to "Content" and the "Copy to Output Directory" property is set to "Copy always".
Screenshot of setting build actions.

This will ensure that when you build the application, it will include these files in the output directory.
At this point, we are setup with the environment. Now, let’s look at interacting with JavaScript and C#.
Interacting with JavaScript from C# and vice-versa
First, let’s create a simple data object (model) that we can use to pass between C# land and JS land. Due to lack of originality, I will create a Person
object as follows:
Code Snippet 5: C# data/model class |
public class Person
{
public Person( string firstName, string lastName, DateTime birthDate)
{
FirstName = firstName;
LastName = lastName;
DateOfBirth = birthDate;
}
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime DateOfBirth { get; set; }
public int SkillLevel { get; set; }
}
|
Next, let’s create a simple Business Logic class that JavaScript can call functions on and interact with our WinForms App. I am also using Json.NET from http://www.newtonsoft.com/ to serialize / de-serialize any data to / from JSON format.
There is absolutely nothing special about the class JavaScriptInteractionObj
class. It’s simply a collection of functions that can be called from JavaScript land.
Code Snippet 6: C# business layer object (equivalent to server-side calls) |
public class JavaScriptInteractionObj
{
public Person m_theMan = null;
public JavaScriptInteractionObj()
{
m_theMan = new Person( "Bat", "Man" , DateTime .Now);
}
public string SomeFunction()
{
return "yippieee";
}
public string GetPerson()
{
var p1 = new Person( "Bruce", "Banner" , DateTime .Now );
string json = JsonConvert.SerializeObject(p1);
return json;
}
public string ErrorFunction()
{
return null;
}
public string GetListOfPeople()
{
List< Person> peopleList = new List< Person>();
peopleList.Add( new Person( "Scooby", "Doo" , DateTime .Now));
peopleList.Add( new Person( "Buggs", "Bunny" , DateTime .Now));
peopleList.Add( new Person( "Daffy", "Duck" , DateTime .Now));
peopleList.Add( new Person( "Fred", "Flinstone" , DateTime .Now));
peopleList.Add( new Person( "Iron", "Man" , DateTime .Now));
string json = JsonConvert.SerializeObject(peopleList);
return json;
}
}
|
Next, we need to bind the object JavaScriptInteractionObj
to the chromium web browser. We do this with the RegisterJsObject()
function.
This function takes the name of the object to be accessed on the JavaScript side and the C# object itself.
Code Snippet 7: Example of Registering a C# object with JavaScript. |
private void buttonRegisterCSharpObject_Click(object sender, EventArgs e)
{
m_chromeBrowser.RegisterJsObject( "winformObj", new JavaScriptInteractionObj());
string page = string.Format("{0}HTMLEmbeddedResources/html/WinformInteractionExample.html" ,
EmbeddedResourceUtils.GetAppLocation());
m_chromeBrowser.Load(page);
}
|
In the C# example above (Code Snippet 7) the name “winformObj
” will be accessible from JavaScript such that the following JavaScript code is possible:
At this point, we have bound the object winformObj
to the window of the Chromium browser. That means, the following code is possible which calls through to GetListOfPeople()
in the C# land.
Code Snippet 8: Example of calling C# code from JavaScript. |
function CallWinformFunc()
{
var list = winformObj.getListOfPeople();
for (var nLoopCnt = 0; nLoopCnt < list.length; nLoopCnt++) {
var person = list[nLoopCnt];
}
}<button onclick="CallWinformFunc()">Test Winform Interaction</button>
|
In the above example, we have an HTML Button that, when clicked, calls the C# function GetListOfPeople()
described in code snippet 6.
Note: One thing I have noticed is that RegisterJsObject()
function should be called straight after the web browser is created. If you do not do this, the call might fail and in JavaScript land, the object will be null
. #
Executing JavaScript Code from WinForms / C#
From the C# side, you can execute any ad-hoc JavaScript code or execute a function simply enough by calling ExecScriptAsync()
. For example, the following code snippet will execute script directly on the browser page turning the background red:
Code Snippet 9: Example of executing JavaScript code from C# |
private void buttonExecJavaScriptFromWinforms_Click(object sender, EventArgs e)
{
var script = "document.body.style.backgroundColor = 'red';";
m_chromeBrowser.ExecuteScriptAsync(script);
}
|
Returning Data from JavaScript land to C# / WinForms
Imagine the scenario where, from C# / WinForms land, you need to find the value of a variable or the value of a function call (to see if it succeeded or not). Using the above method, we do not get a return value from the
The frequently asked questions for CefSharp explains that this method only returns simple data types (int
s, bool
s, string
). Let’s imagine we want to execute the following ad-hoc code in snippet 10 which returns an int
.
Code Snippet 10: JavaScript function we will use to call from C# |
function tempFunction() {
var w = window.innerWidth;
var h = window.innerHeight;
return w*h;
}
tempFunction();
|
This is how we would execute that code using Chromium.
Code Snippet 11: Example of executing JavaScript code that returns a value from C# |
private void buttonReturnDataFromJavaScript_Click(object sender, EventArgs e)
{
StringBuilder sb = new StringBuilder();
sb.AppendLine("function tempFunction() {");
sb.AppendLine(" var w = window.innerWidth;");
sb.AppendLine(" var h = window.innerHeight;");
sb.AppendLine("");
sb.AppendLine(" return w*h;");
sb.AppendLine("}");
sb.AppendLine("tempFunction();");
var task = m_chromeBrowser.EvaluateScriptAsync(sb.ToString());
task.ContinueWith(t =>
{
if (!t.IsFaulted)
{
var response = t.Result;
if ( response.Success == true )
{
MessageBox.Show( response.Result.ToString() );
}
}
}, TaskScheduler.FromCurrentSynchronizationContext());
}
|
The above example demonstrates returning a simple int
value from JavaScript.
But what if we want to return a complex object from JavaScript to C#?
Well, remember that the function EvaluateScriptAsync()
only returns simple data types (int
s, bool
s, string
). Therefore, if you need to return a complex object, you need to convert it to JSON first and send the object back as a string
.
The following code example demonstrates returning a complex object (a person
object) from JavaScript to C#.
Code Snippet 12: Example of executing JavaScript code that returns a value from C# |
private void buttonReturnDataFromJavaScript2_Click(object sender, EventArgs e)
{
StringBuilder htmlPage = new StringBuilder();
htmlPage.AppendLine("");
htmlPage.AppendLine("");
htmlPage.AppendLine("");
htmlPage.AppendLine("");
htmlPage.AppendLine("Hello world 2");
htmlPage.AppendLine("");
m_chromeBrowser.LoadHtml(htmlPage.ToString(), "http://customrendering/");
StringBuilder sb = new StringBuilder();
sb.AppendLine("function tempFunction() {");
sb.AppendLine(" // create a JS object");
sb.AppendLine(" var person = {firstName:'John', lastName:'Maclaine', age:23, eyeColor:'blue'};");
sb.AppendLine("");
sb.AppendLine(" // Important: convert object to string before returning to C#");
sb.AppendLine(" return JSON.stringify(person);");
sb.AppendLine("}");
sb.AppendLine("tempFunction();");
var task = m_chromeBrowser.EvaluateScriptAsync(sb.ToString());
task.ContinueWith(t =>
{
if (!t.IsFaulted)
{
var response = t.Result;
if (response.Success == true)
{
MessageBox.Show(response.Result.ToString());
}
}
}, TaskScheduler.FromCurrentSynchronizationContext());
}
|
As you can see above, in the temporary JavaScript code, we declare a person
object like so:
var person = {firstName:"John", lastName:"Maclaine", age:23, eyeColor:"blue"};
The last thing we do is to convert the object to a string
using the “JSON.stringify()
” method. Once back in C# land, we could use Newtonsoft JSON.NET to convert the string
back to an object.
Lower Case Function Calls in JavaScript Land
One thing to look out for is the lower casing of C# method calls from JavaScript land. As you can see below, the C# class has a method named SomeFunction()
that returns a sting
. Notice the function starts with an uppercase "S
".
If you look at the chrome-dev-tools window, you can see that when you attempt to call the function with an upper case letter, you get a Type Error (undefined function). Chrome lowercases the function names. When you call the same function but with a lower case "S
", it works.

HTML5 Demo Functionality
To scratch the surface of what’s possible, I have included a number of demos in the sample forms. Some of these HTML forms interact with WinForms. Others simply show off the features/possibilities that are available by including Chromium in your project.
I have included Bootstrap in a couple of examples to demonstrate receiving user input from HTML. In another example, I have used the charting library AmCharts
to display a simple line graph. Other examples show off canvas functionality and WebGL functionality.




Conclusion
This was a brief introduction to integrating the Chromium web browser into a WinForms application to leverage HTML as a User Interface. The Chromium web browser is a complete and comprehensive modern framework for embedding such functionality. In a nutshell, the Chromium Embedded Framework is a fantastic framework for embedding an HTML5 based GUI in a WinForms application.
Resources
Chromium Dependencies
After including Chromium in your project, you will find a lot more DLLs in your project folder. This page: https://github.com/cefsharp/cef-binary/blob/master/README.txt describes these files and I have included them here for completeness.
Description
|
Files
|
Notes
|
CEF core library
|
libcef.dll
|
|
Unicode support
|
icudtl.dat
|
|
Localized resources
|
locales/ directory
|
Contains localized strings for WebKit UI controls.
A .pak file is loaded from this folder based on the CefSettings.locale value.
Only configured locales need to be distributed. If no locale is configured the default locale of "en-US " will be used.
Locale file loading can be disabled completely using CefSettings.pack_loading_disabled . The locales folder path can be customized using CefSettings.locales_dir_path .
|
Other resources
|
cef.pak
cef_100_percent.pak
cef_200_percent.pak devtools_resources.pak
|
Contains WebKit image and inspector resources. Pack file loading can be:
- Disabled completely using
CefSettings.pack_loading_disabled .
- The resources directory path can be customized using
CefSettings.resources_dir_path .
|
FFmpeg audio and video support
|
ffmpegsumo.dll
|
Without this component, HTML5 audio and video will not function.
|
PDF support
|
pdf.dll
|
Without this component, printing will not function.
|
Angle and Direct3D support
|
d3dcompiler_43.dll (required for Windows XP)
d3dcompiler_47.dll (required for Windows Vista and newer)
libEGL.dll
libGLESv2.dll
|
Without these components, HTML5 accelerated content like 2D canvas, 3D CSS and WebGL will not function.
|
Windows Vista 64-bit sandbox support (32-bit distributions only)
|
wow_helper.exe
|
Without this component, the 32-bit build of CEF will not run on 64-bit Vista machines with the sandbox enabled.
|
Errata
If / when you see any errors, or have ideas for better ways of doing something, then please feel free to comment and communicate back. Feedback is always welcome!
History
24-02-206 - Github Project updates & fixes provided courtesy of Alex Maitland
- Upgrade to CefSharp 47.0.2
- Remove packages and bin folders
- Remove AnyCpu target from solution
- Show DevTools is now an extension method that comes from the CefSharp namespace
- Remove empty folders from project
- Fix up bootstrap example urls - incorrect path
- Cef.Initialize is now called by default in ChromiumWebBrowser
- Remove Cef.Shutdown call - it will be automatically called when exiting the application