Electron APP and classic windows APP integration

Surendra Ganti
5 min readMay 31, 2018

--

Electron is new kid in the town to develop all the desktop applications, supporting multiple platforms and using javascript (node.js)+ HTML as a way to create the UI, provides a lot of flexibility to the current gen developers.

We faced an interesting problem this week, where we have to improve our UI experience in our classic windows application. There are two options infront of us.

  1. Upgrade parts of the app in WPF (Windows presentation framework)
  2. Use Electron and integrate it with our classic windows APP.

Using WPF

I have my own strict opinions on using WPF.

  1. WPF is on life support, no major changes or investments since 2015 (3 years) and no new feature speaks for itself.
  2. WPF team blog itself is inactive since 3 years (https://blogs.msdn.microsoft.com/wpf/)
  3. Finiding developers with XAML knowledge is always difficult, due to its steep learning curve.
  4. No clear path to succession to Web apps any more (after silverlight got killed).

Basing on the above factors, and mainly due to points 3 & 4, I am a bit hesitant to use WPF and want to explore the other options.

Using Electron

I see the below advantages by using Electron rather than WPF

  1. Open source and lot of community support, supported by github itself.
  2. cross-platform
  3. can be developed using HTML+ Javascript, meaning atleast some percentage of code can be re-used to webapp.
  4. There are huge number of developers out there who can work on HTML + Javascript, or learning HTML & Javascript is small learning curve compared to XAML.

Problem & crux of this post

I decided to use electron, but we don’t want to convert all our application to electron overnight, it will need massive investment. We need a way to integrate Electron desktop app to communicate effectively with winform dekstop app and vice-versa.

Solution

Use the grand-pa of communication TCP/IP.

  1. Raise an event from your electron app, using IPC render and IPC main modules.
  2. In the main module use node.js net.socket module to create a TCP client and join on a socket.
  3. In the winforms app, create new TCP Server which start at the launch of the application.
  4. Upon receiving the data from TCP Channel open the requisite form accordingly.

Code snippets

  1. Raising an event from electron app using IPC render can be done as below
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Hello World!</title>
<!--Without the below script block, jQuey will not get executed in electron window -->
<script>
if (typeof module === 'object') {
window.module = module;
module = undefined;
}
</script>
<script src="./jquery.js"></script>
<script src="./renderer.js"></script>
<!--Without the below script block, jQuey will not get executed in electron window -->
<script>
if (window.module) module = window.module;
</script>
</head>
<body>
<h1>Test App</h1>
<script>
$(document).ready(function () {
const ipc = require('electron').ipcRenderer;
$("#openForm1").click(function (event) {
//$("h1").append("test");
ipc.send('openForm1');
});
$("#openForm2").click(function (event) {
//$("h1").append("test");
ipc.send('openForm2');
});
$("#closeAll").click(function (event) {
//$("h1").append("test");
ipc.send('closeAll');
});
});
</script>
<br/>
<br/>
<br/>
<table>
<tr>
<td>
<input type="button" id="openForm1" value="Open Form 1" />
<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>
</td>
<td>
<input type="button" id="openForm2" value="Open Form 2" />
<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>
</td>
<td>
<input type="button" id="closeAll" value="close All" />
</td>
</tr>
</table>
<br/>
</body>
</html>

Connecting from electron app to TCP server can be achieved in the main.js

// Modules to control application life and create native browser window
const {
app,
BrowserWindow
} = require('electron')
const path = require('path');
const url = require('url');
const net = require('net');
const electron = require('electron');
const ipc = electron.ipcMain;
const shell = electron.shell;
// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let mainWindow
function createWindow() {
// Create the browser window.
mainWindow = new BrowserWindow({
width: 800,
height: 600
})
// and load the index.html of the app.
mainWindow.loadFile('./index.html')
// Open the DevTools.
// mainWindow.webContents.openDevTools()
// Emitted when the window is closed.
mainWindow.on('closed', function () {
// Dereference the window object, usually you would store windows
// in an array if your app supports multi windows, this is the time
// when you should delete the corresponding element.
mainWindow = null
})
}
var HOST = ''; # ip address for the socket
var PORT = 8000; # port number for the socket
var client = new net.Socket();
client.connect(PORT, HOST, function () {
console.log('CONNECTED TO: ' + HOST + ':' + PORT);
// Write a message to the socket as soon as the client is connected, the server will receive it as message from the client
client.write('I am Chuck Norris!');
});// Add a 'data' event handler for the client socket
// data is what the server sent to this socket
client.on('data', function (data) {
console.log('DATA: ' + data);
client.write('received: ' + data);
// Close the client socket completely
});// Add a 'close' event handler for the client socket
client.on('close', function () {
console.log('Connection closed');
});
ipc.on('openForm1', function (event) {
client.write('openForm1');
});
ipc.on('openForm2', function (event) {
client.write('openForm2');
});
ipc.on('closeAll', function (event) {
client.write('closeAll');
});
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', createWindow)
// Quit when all windows are closed.
app.on('window-all-closed', function () {
// On OS X it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q
if (process.platform !== 'darwin') {
app.quit()
}
})
app.on('activate', function () {
// On OS X it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (mainWindow === null) {
createWindow()
}
})

A TCP Server on your C# windows App can be created from this code project.

A sample code snippet to open the forms in C# is as below

public tcpManager()
{
tcpServer1 = new TcpServer();
form1 = new Form1();
form2 = new Form2();
}
public bool openTCPConnection()
{
this.tcpServer1.IdleTime = 50;
this.tcpServer1.IsOpen = false;
this.tcpServer1.MaxCallbackThreads = 100;
this.tcpServer1.MaxSendAttempts = 3;
this.tcpServer1.Port = -1;
this.tcpServer1.VerifyConnectionInterval = 0;
this.tcpServer1.OnDataAvailable += new tcpServer.tcpServerConnectionChanged(this.tcpServer1_OnDataAvailable);
tcpServer1.Close();
tcpServer1.Port = 3001;
tcpServer1.Open();
return true;
}
private void tcpServer1_OnDataAvailable(tcpServer.TcpServerConnection connection)
{
byte[] data = readStream(connection.Socket);
if (data != null)
{
string dataStr = Encoding.ASCII.GetString(data);
if (dataStr == "openForm1") {
//open form1
openForm(new Form1());
closeForm(new Form2());
} else if (dataStr == "openForm2")
{
openForm(new Form2());
closeForm(new Form1());
}
else if (dataStr == "closeAll")
{
closeForm(new Form1());
closeForm(new Form2());
}}
}

--

--

Surendra Ganti

A techno functional expert in the financial domain, A Versatilist, with a lot of experience in developing on SQL Server. Thoughts and opinions are my own.