It has become increasingly common to expect desktop applications to be capable of integrating to the web to incorporate available external information.
There are several ways this could be achieved: by consuming web services to get the raw data; or, by simply including a browser component within the application, in the form of a panel. In this second case, we’ll be capable of navigating to sites displaying up-to-date information or external multimedia content, such as maps, videos, images, and sound —as an example, visit Fish Facts, a live demo that shows basic GUI virtualization capabilities as well as an integration with the web browser and external web resources.
Once we transform these applications with Thinfinity® VirtualUI™, and we invoke them from the web, an effect similar to the famous Russian Nested Dolls case occurs: we run from the browser an application that contains within itself another browser. In short, we surf to an application that internally re-surfs. This is not bad in itself, but we can take advantage of already being in the web to access the final resource directly from the main browser. By doing this we will avoid a roundabout that, among other things, would unnecessarily increase the consumption of resources.
Let’s take a look at the diagrams of the same application running on a desktop…
… and from the Web:
As we can see, when we run the application from the desktop we must necessarily navigate through an embedded Webbrowser component. The application is the one that connects to the desired web address, and this webpage is seen in an internal panel, within the application.
But once we load the application from a browser we would experience the aforementioned effect. As a result, the application browses on its own, and the visible result of that surfing is re-transferred to our browser.
If we transfer that navigation to the only browser we need to use, we will access the intended resource without delay or additional bandwidth, processing, or time consumption.
The demo application
The application we use in the example attempts to break this browser-within-the-browser effect, simply by using the internal browser only when the application is accessed from the desktop. If the application is being accessed from the web, instead of using that component, the app would load the desired page in an iframe created on the fly. To achieve this integration (which requires a little bit more interaction between the application and the browser) we will add a jsRO object, which will control the remote iframe from the application.
The first addition to our application will be the addition of VirtualUI and its initialization:
using System;
using System.Windows.Forms;
namespace matryoshka
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
new Cybele.Thinfinity.VirtualUI().Start();
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
}
}
Let’s now see what we have to add to the main form code, which it only has a panel where to enter the browsing URL and another one where the WebBrowser component is located.
The following is the application code before adding the changes:
using System;
using System.Drawing;
using System.Windows.Forms;
namespace matryoshka
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
// Sets Google Maps URL to run in the embedded browser
txtURL.Text = "https://maps.google.com";
Navigate();
}
private void btnGo_Click(object sender, EventArgs e)
{
Navigate();
}
private void txtURL_KeyPress(object sender, KeyPressEventArgs e)
{
if (Convert.ToInt32(e.KeyChar) == 13)
{
Navigate();
}
}
private void Navigate()
{
if (!txtURL.Text.Equals(""))
{
if (!txtURL.Text.StartsWith("http"))
{
txtURL.Text = "http://" + txtURL.Text;
}
webBrowser1.Navigate(new Uri(txtURL.Text));
}
else {
webBrowser1.Navigate(new Uri("about:_blank"));
}
}
}
}
As a first step, we will add to the class the objects in the Cybele.Thinfinity library:
using System;
using System.Drawing;
using System.Windows.Forms;
using Cybele.Thinfinity;
namespace matryoshka
{
public partial class Form1 : Form
{
private VirtualUI vui = new VirtualUI();
private IJSObject mRemoteLayout = null;
public Form1()
...
This demo application loads a Google Map. To be run embedded in an iframe, Google Maps needs a different URL from the one used on a full page. We know that it will run embedded in the iframe (using VirtualUI) and directly when loaded into the internal browser.
public Form1()
{
InitializeComponent();
if (vui.Active)
{
// Code executed only when the application runs with Thinfinity VirtualUI
// Sets Google Maps URL to run embedded in an iframe
txtURL.Text = "https://www.google.com/maps/embed";
}
else
{
//* Code executed only when the application runs from desktop
// Sets Google Maps URL to run in the embedded browser
txtURL.Text = "https://maps.google.com";
}
}
Additionally, we must also make some changes to Navigate( ):
private void Navigate()
{
string sURL = "";
if (txtURL.Text.Equals("")){
sURL = "about:_blank";
}
else
{
sURL = txtURL.Text;
if (!sURL.StartsWith("http"))
{
sURL = "http://" + sURL;
}
}
if (vui.Active) {
mRemoteLayout.Properties["url"].AsString = sURL;
}
else {
webBrowser1.Navigate(new Uri(sURL));
}
}
The last (but not least important) addition is the one concerning the jsRO object management: the first method, for its instantiation and definition of their getters; the other one, for the later updating of the iframe’s bounds.
private void Form1_Shown(object sender, EventArgs e)
{
// Creates jsRO object for iframe control
mRemoteLayout = new JSObject("layout");
mRemoteLayout.Properties.Add("windowId").AsString =
String.Format("virtualui_canvas_{0}", Handle);
mRemoteLayout.Properties.Add("url").AsString = txtURL.Text;
mRemoteLayout.Properties.Add("bounds")
.OnGet(new JSBinding( // Adds a "getter" to bounds
delegate(IJSObject AObj, IJSProperty AProp)
{
// Returns a JSON object
Point p = pnlNav.PointToScreen(new Point(0, 0));
p.Offset(-Left, -Top);
AProp.AsJSON = "{" + String.Format("\"left\":{0}, \"top\":{1}, \"width\":{2}, \"height\":{3}",
p.X, p.Y, pnlNav.Width, pnlNav.Height) + "}";
}));
mRemoteLayout.ApplyModel();
}
private void pnlNav_Resize(object sender, EventArgs e)
{
// Updates all mRemoteLayout's properties "getters"
if (mRemoteLayout != null)
{
mRemoteLayout.ApplyChanges();
}
}
We can now introduce the additional information that must be included, at the javascript level, to the HTML page.
What follows is the base page to which we will be adding the functionality:
We will now create three global variables for the VirtualUI and jsRO objects, and for the iframe created on the fly.
<script type="text/javascript">
var virtualUI = null;
var jsro = null;
var nav = null;
$(document).ready(function () {
...
We will add three functions intended to create the iframe and keep it updated:
...
$(document).ready(function () {
var navigate = function(url) {
nav.src = url;
}
var applyBounds = function(bounds) {
nav.style.left = bounds.left + "px";
nav.style.top = bounds.top + "px";
nav.style.width = bounds.width + "px";
nav.style.height = bounds.height + "px";
}
var createNav = function () {
nav = document.createElement("iframe");
nav.id = "nav";
nav.style.position = "absolute";
nav.style.display = "none";
nav.style.zIndex = 2;
nav.style.border = "none";
}
...
We will now instantiate the VirtualUI object, configure its events, and then connect to the application:
var createNav = function () {
...
...
}
virtualUI = new Thinfinity.VirtualUI();
virtualUI.onError = function (errorMsg) {
alert("Application load failed");
};
virtualUI.onLoading = function () {
console.log((virtualUI.devMode) ? "Waiting for application..." : "Loading...");
};
virtualUI.onClose = function (url) {
if ((typeof url != 'undefined') && (url != '') && (url != null)) {
if ((virtualUI.devMode != true) || (window.top.opener)) {
window.top.close();
}
window.top.location.href = url;
return;
}
if (virtualUI.devMode) { location.reload(); }
if ((virtualUI.devMode != true) || (window.opener)) { window.close(); }
if ((window.top == window) && (document.referrer) && (location.href != document.referrer)) {
location.href = document.referrer;
}
else {
if (nav) { nav.style.display = "none"; }
alert("Application closed");
}
};
// -- Connect to server...
virtualUI.connect();
Finally, we will create the jsRO object instance. Please note that it’s in the creation of the layout jsRO model (defined in the application) where the iframe is injected into the application window. The other two events are used to keep both the URL and the inserted iframe bounds updated:
...
virtualUI.connect();
// Defining Javascript Remote Objects Elements
jsro = new Thinfinity.JsRO();
jsro.on('model:layout', 'created', function (obj) {
layout = jsro.model.layout;
if (nav == null) {
createNav();
document.getElementById(layout.windowId).appendChild(nav);
nav.style.display = "inline-block";
}
applyBounds(layout.bounds);
});
jsro.on('model:layout.url', 'changed', function (obj) {
navigate(obj.value);
});
jsro.on('model:layout.bounds', 'changed', function (obj) {
applyBounds(layout.bounds);
});
With this procedure, we’ve been able to break the “Matryoshka Effect”. From now on, the embedded browser will be used from the desktop, while the external one will be used from the web —but always being controlled by the remote application.
Running the application from the browser
To see the end result, we must first add the application to the list of registered apps in the Thinfinity VirtualUI Server and configure its home page, making sure it points to the page modified by us in this tutorial.
Once this is done, we can access the application from the browser. If we are running the application from the Thinfinity VirtualUI Development Lab, we must change the virtual path’ address so as to match the one defined in the configuration —in the VirtualUI’s manager.