Click here to Skip to main content
15,867,568 members
Articles / Web Development / HTML
Article

Sciter/HTML/C# Based Desktop Apps Walkthrough

Rate me:
Please Sign up or sign in to vote.
4.96/5 (92 votes)
17 Jan 2019CPOL31 min read 145.4K   3.2K   143   42
Create HTML/CSS/script based cross-platform desktop apps in C# with Sciter engine!

Introduction

This article shows how to use Sciter through C# for creating HTML based desktop apps by using the SciterSharp library (.NET bindings over Sciter API). For quick starting our application, we use a Sciter Bootstrap template which helps in making our app compilable across Windows, Linux and OSX.

Sciter is a multi-platform HTML rendering engine for making desktop apps. Among the cool features, it has supports for the following:

  • native integration for UI <-> host communication, DOM manipulation, resources tracking, all via C#
  • a good amount of CSS3 with a nice layouting system using flex units
  • scripting via TIScript language (JavaScript extension)
  • TIScript API for frontend DOM/style manipulation, AJAX calls, JSON, ...

It is also free for commercial use, though it is not open-source. It is distributed as a native shared library (DLL) which you find inside the C/C++ SDK. The SDK contains builds for Windows, Linux and OSX.

Sciter technology is used in the real world by some big software, proving how far you can go with it: ICQ client, Norton, Avast, Bitdefender, ESET antivirus.

Image 1

The sample desktop app uses 'Google API Client Library for .NET' to query Google Fonts for showing the user a list of available fonts and a button to download each of them. This will require a mix of native C# coding as well as HTML and scripting, for making the UI, and communicating between those 2 layers. This way, you will get familiar with how to create applications that deal with a bit of native C# coding, and with asynchronous resource loading and in how to pass data between UI/native layer. The complete source is also available at GitHub.

Image 2

Considerations of Sciter Instead of, say, CefSharp or Electron.io

There are already plenty of options for embedding HTML content in a .NET app. CefSharp and Electron.io seem to be the most popular ones, and both use Chromium as the underlining engine. Since Sciter is not a standard engine used in a popular web-browser, then why would I choose it instead of a well known HTML engine like Gecko or Chromium with cutting edge HTML5 technologies support? Well, you need to decide for yourself what’s right for you and what’s right for your project. What I can do is tell you what made me adopt Sciter, how to use the technology, and also address its drawbacks.

Sciter Specific Feature-Set

Sciter doesn't try to implement HTML5 standards, but it does NOT mean that your prior HTML/CSS knowledge can't be used. Here is a summary of what you may need to account about the differences:

  • CSS accounts all essential properties (full CSS2.1) and some CSS3 important stuff (border-radius, box-shadow, linear-gradient, see CSS properties support sheet; also CSS layouting is a bit Sciter specific, made mainly through flex units and the 'flow:' property, which is closely the equivalent to CSS3 flexbox.
  • HTML/CSS support has some subtleties, here is the best resource for knowing about them.
  • Scripting is made through TIScript language; it does not conform with standard browser API, it has its own API set which is well documented; but since it is a JavaScript extension, web-programmers will feel completely familiar with it (see comparison chart)
  • Among HTML5 features, Sciter has equivalents for the following: canvas drawing, SVG, WebSockets API, <video>, CSS animations/transition, custom fonts (you can use for example FontAwesome); other HTML5 features you might be able to achieve with a bit of additional native coding.

Advantages of Using Sciter

The reason why I choose Sciter over the other engines is that, first and foremost, Sciter is tailored for making desktop apps, and not web-browsers. I think that such commitment brought a lot of goodness in how it has been developed over time (already 10 years of road).

My advice for Sciter usage is when you want good native support for your HTML app: you have all the flexibility for choosing your favorite programming language (D, C#, C++, Python, Delphi, Go) and it has a mature native API for manipulating every aspect of your app.

Standard browser engines are designed to access pages provided remotely from a web-server, executing them in a sandbox where system resources are strictly controlled. Sciter is designed to handle resources from any source: resources loading is completely customizable, and it also has an API for loading resources from BLOB's packed data.

In TIScript, you can deal with system resources (file, sockets, IPC) and easily talk with native layer. Sciter doesn't have many security constraints and the associated overhead because, as you are making desktop apps, it is assumed you are dealing with execution of trusted resources.

Chromium is really bloated for small to medium applications. Integration of Chromium to the native layer has serious issues. For example, as said in this article, you can't get complex JavaScript objects to the C# layer, you need to convert it to JSON first and send the object back as string.

  • Sciter is not just cross-platform, supporting Windows, Linux/GTK and OSX, but it also has bindings for many programming languages: C#, C++, D, Python, Delphi and Go (I am the author and maintainer of the C# and D ones)
  • Lightweight: Engine is a single native DLL that you need to ship along with your product
  • Performant: There is no delay in engine startup, has lower memory consumption if compared to others bloated HTML engines, and the drawing backend is GPU accelerated (Direct2D or Skia/OpenGL in Windows, Cairo in Linux and CoreGraphics in OSX)
  • Excellent native windowing integration: Creation of multiple Sciter window instances, all of them share the same TIScript VM so you can share data in between; Aero-DWM-enabled windows, WS_EX_LAYERED windows (desktop transparency); allows creating native windows/HWND as child DOM elements
  • Customizable native resource loading; customizable debug output messaging
  • Native APIs for DOM handling, DOM events callbacks, and TIScript <-> native integration
    • CefSharp doesn't expose a DOM API; with SciterSharp, you can view/manipulate the DOM from C#! It is most useful for debugging, since DOM manipulation you usually do through scripting
  • As of version 3.3.1.4, you can use it to render UI in DirectX windows, see here and image below:

Image 3

Drawbacks of Using Sciter

  • It is not a WYSIWYG environment like WebForms or WPF, you need to write HTML code and preview the resulting visual in the sciter.exe tool (well, if you consider HTML as a good methodology over WYSIWYG, then it really is not a disadvantage).
  • Lacks HTML5 functionalities and W3C standards: you can't simply grab a library like JQuery or Bootstrap and use it in Sciter
  • Linux/GTK support has many HTML/CSS missing features if compared to the Windows version which is more mature

1. Getting Started

To get started, you must have in mind what are the goals we will be achieving in this article and what is involved:

  • We will walk-through creating a desktop app that shows a list of available Google Fonts with a button letting user download a .zip with the entire font family.
  • This entire article will be done in Visual Studio 2015, but at the end I will show how to compile it in Linux using MonoDevelop.
  • The frontend/UI is made entirely in HTML/CSS and scripting with TIScript.
  • The backend is made in C# and involves:
    • Google .NET API: for querying Google Fonts
    • Handling Google response asynchronously and passing the returned data to UI layer
    • We use SciterSharp library and its API over Sciter engine for many things, from UI communication to resource loading
  • We start off from a pre-made IDE project we download on-line from Sciter Bootstrap page.
    • We choose a multi-platform template so we can easily compile for Windows, Linux and OSX.

So to get started, we need to acquire some resources, so follow the 3 steps below.

Step 1: Download Sciter Bootstrap Package

As we are going to create a multi-platform desktop app, you must understand that supporting different OS means dealing with different API sets. Normally, you want to work with a unified API that works across multiple platforms. But sometimes, it is not possible and you have to resort to using OS specific APIs. In C#, you do that by surrounding platform specific code with #if/#endif conditional compilation blocks. This situation might be required in our case since we are dealing with two different OSes and their respective windowing system: Win32 API and GTK+3 API.

Fortunately, you don't need to worry too much about it because we will start with a template with the minimal boilerplate code done. And it is not just about code, it also contains the proper SciterSharp dependency configured, a .sln that you can open in VS and MonoDevelop for compiling your app, and it contains two projects (one for each platform/IDE).

So, go to Sciter Bootstrap download page and follow these steps:

Step 1: Type the title of the project - for this article, we use FontLister (title name must obey C# identifiers rules)

Step 2: Select 'Visual Studio + Xamarin project' (the first radio button)

Step 3: Click 'Download' button

Extract the .zip content and open the .sln in Visual Studio 2015. Press the Build button, wait for it to download SciterSharp NuGet package, and then run the app. Cooool, you already have a Sciter multi-platform app running. It was easy, no?

If you get compile errors, it is because SciterSharp NuGet was not properly downloaded. Go to Package Manager Console and try issuing 'Update-Packages -Reinstall' command to fix it.

Note that this solution contains three projects. As we don't need to build the MONO/GTK and OSX projects in Windows (it won't even run), feel free to right-click those projects and select 'Unload project'.

Manually Installing

SciterSharp can be download and installed on your own if you are starting a project and don't want to use Sciter Bootstrap.

However, those methods are not supported for this article because we want to start with Sciter Bootstrap template code since it is multi-platform ready and we want our app to run in Windows, Linux and OSX.

Step 2: Install NuGet Packages and Get a Google API Key

We need additional .NET libraries via NuGet packages. You have to install the packages to both projects (FontListerGTK and FontListerWindows). You can do it right clicking the 'FontLister' solution item and 'Manage NuGet Packages for Solution' (note that FontListerGTK project must be loaded).

Install these NuGet packages:

  • Google.Apis.Webfonts.v1
  • Newtonsoft.Json

You need to get yourself a Google API key for querying Google Fonts.

  • Go to Google Developers Console
  • Create a new project
  • In the overview page, there is an input which reads 'Search all 100+ APIs'; search for 'fonts'
  • Select 'Web Fonts Developer API' and click the blue 'Enable API' button
  • Now go to Credentials / New credentials / Server key
  • Since we will query only Google Fonts locally from a desktop app, a simple Server key is enough
  • Press Create and a popup will show the API key, just save it somewhere

Step 3: Download Sciter SDK

Grab the Sciter SDK from here. The SDK is mainly for developers using the C/C++ API, but it also has all Sciter binaries and tools. What we need from the SDK is the sciter.exe tool found in the /bin directory. I recommend you to run it and pin the icon in the taskbar. We will use this tool for 2 things:

  1. You use it for viewing the result of the HTML code that you write; you normally edit your HTML code, and switch back to sciter.exe and press F5 to refresh the page; that is, sciter.exe is like a browser for Sciter HTML content; it also has the F12 tool equivalent for DOM inspecting and script debugging.
  2. It also gives you access to the documentation that comes bundled inside the SDK; click the ? mark button in the left toolbar, and a window opens containing a description of all TIScript API that you use for scripting and also teaches you about the language; you will be consulting this a lot, trust me.

2. Hands on Code -> Backend

Before we get started, I want to advise you about a common source of frustration we face when doing such kind of desktop application.

It is common practice in Web development to separate work between two persons: one responsible for the UI/UX thinking (frontend), and other responsible for the business logic, which drives the application based on UI events (backend).

With Sciter, as in Web development, you should stick with this same of work-flow. The problem is that you will be alone and will inevitably be doing both things at the same time, and it can be very frustrating.

When doing UI coding and thinking, normally, with a bit of CSS and HTML, you achieve results very fast and you can visually see the result. The backend normally requires more mentally tough work and a procedural way of thinking. The problem is that these two modes of thinking are not compatible, it really hurts my brain when I mix doing both things. As so, I really encourage you to strive for getting used to doing these things separately, as I will show you.

We will start with the backend where, prior to doing the actual UI, we will define the connections points with UI using 2 different idioms. In Sciter, there are 3 idioms for backend <-> UI communication which you can read in details here.

This is a walk-through where we will essentially add or adjust code from the default code from the Bootstrap template.

Window Creation

The initialization of our app is in file Src/App.cs, where you find the window creation code, which is self-explanatory. The SciterWindow class is an agnostic method for creating and handling a OS native window for hosting a Sciter HTML page. For our FontLister, I changed the title and size of the window, so I ended with:

C#
// Create the window
var wnd = new SciterWindow();
wnd.CreateMainWindow(800, 600);
wnd.CenterTopLevelWindow();
wnd.Title = "Font Lister";

In Windows only, you could derivate a new C# class from SciterWindow class to override ProcessWindowMessage() virtual method so you can process Win32 messages.

Theoretically, with just a SciterWindow instance, you can already load and display a HTML page by calling LoadHtml(), for example:

C#
wnd.LoadHtml("<html><body>Hello World!</body></html>");
wnd.Show();

But we want to get deeper and control everything about the loading of the HTML page, and that's why we need a SciterHost instance to wrap the HTML loading procedure.

Page Hosting Explained

SciterHost class from SciterSharp lib is the central component for controlling many aspects along the life of the hosted HTML page. Essentially, it lets you track the following notifications that Sciter engine generates:

  • SC_LOAD_DATA and SC_DATA_LOADED allows you to manipulate resources loading and is explained in the next section.
  • SC_POSTED_NOTIFICATION is received after you call SciterHost.PostNotification() method; it is useful in multi-thread scenarios because this message is always received in the UI thread; SciterHost provides a InvokePost() method that you can use from a worker thread to execute a given delegate in UI thread.
  • SC_ATTACH_BEHAVIOR: a request to attach a native behavior (SciterEventHandler instance) to a DOM element.
  • SC_ENGINE_DESTROYED: generated just before engine is destroyed, after main window is closed.

SciterHost class has corresponding overridables methods for you to handle each of these notification.

Also, SciterHost allows you to attach a window-level event handler via AttachEvh() method. This handler receives every event from the page before it is dispatched to the target DOM element.

Actually, Bootstrap extends SciterHost with the Host class, so hosting related code is located in a separate class/file (Src/Host.cs).

C++
// Prepares SciterHost and then loads the page
var host = this;
host.Setup(wnd);
host.AttachEvh(new HostEvh());
host.SetupPage("index.html");
wnd.Show();

Note that prior to loading the HTML page, we must first call Setup() to associate a SciterWindow to SciterHost, and also optionally attach a window-level SciterEventHandler with the AttachEvh() call in order to receive the events of the page that is about to be loaded with SetupPage().

Resource Loading from PACKED Data

With SC_LOAD_DATA and SC_DATA_LOADED notifications, you can track and customize the loading of every resource requested by your HTML page (HTML page itself, images, scripts, CSS) or you can just ignore it and leave the default loading mechanism.

Notice that in the BaseHost class from Host.cs file, the Bootstrap code uses a custom loading strategy by overriding OnLoadData() method:

C++
protected override SciterXDef.LoadResult OnLoadData(SciterXDef.SCN_LOAD_DATA sld)
{
    if(sld.uri.StartsWith("archive://app/"))
    {
        // load resource from SciterArchive
        string path = sld.uri.Substring(14);
        byte[] data = _archive.Get(path);
        if(data!=null)
            _api.SciterDataReady(_wnd._hwnd, sld.uri, data, (uint) data.Length);
    }
    return SciterXDef.LoadResult.LOAD_OK;
}

Sciter resources are tracked via standard URL mechanism. What we are doing is, if URL of the resource starts with archive://app/ (i.e., the protocol), we load the resource from our SciterArchive instance which holds all our resources in a BLOB packed format. This BLOB is contained in file ArchiveResource.cs and is recreated every time we build the project through a Pre-build command that is already configured in the project.

Loading from SciterArchive BLOB only happens in RELEASE mode. In DEBUG mode, resources are loaded directly from the file system. Why? Because our RELEASE build will contain all resources packed inside the .exe making deploying easier, but our DEBUG build will be faster by loading resources from file system.

BaseHost() static method of BaseHost class is responsible for making this differentiation between DEBUG and RELEASE mode loading strategy:

C++
static BaseHost()
{
#if DEBUG
    _rescwd = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location).Replace('\\', '/');
#if OSX
    _rescwd += "/../../../../../res/";
#else
    _rescwd += "/../../res/";
#endif

    _rescwd = Path.GetFullPath(_rescwd).Replace('\\', '/');
    Debug.Assert(Directory.Exists(_rescwd));
#else
    _archive.Open(SciterAppResource.ArchiveResource.resources);
#endif
}

To load the initial HTML page, it specifies the page URL with protocol as: file:/// or archive://app/ depending on whether it is compiling in DEBUG or RELEASE. It is the URL of the initial page that defines the base URL used for resolving relative URL, that is, every relative URL (like for links, images, frames, CSS or script import) will be resolved with the base URL prepended.

GOOGLE 1 - Loading and passing GOOGLE font list to UI layer. The "application event" idiom (host -> UI)

For dealing with Google Fonts, we create a dedicated C# class. The entire class code is in file Data/GAPI.cs which you can copy from the finished sample .zip.

The first task this class needs to address is to request via Google API a list of available Google fonts and pass this list to UI layer. Since this operation requires time since it fetches data from internet, you don't want this processing to occur in main UI thread, but rather, in a background thread. So here is the code that achieves it:

C++
public static void Setup()
{
    new Thread(() =>
    {
        var service = new WebfontsService
                      (new Google.Apis.Services.BaseClientService.Initializer()
        {
            ApiKey = API_KEY
        });

        var request = service.Webfonts.List();
        request.Sort = WebfontsResource.ListRequest.SortEnum.Popularity;

        _fontlist = request.Execute().Items;// if you get an Exception here,
                                            // please disable your Firewall!
        Debug.Assert(_fontlist.Count > 0);
        ...

Make sure you have got your Google API key set to API_KEY variable. The returned list of Google Fonts is stored in a static variable '_fontlist' cause we will need the list later.

Now we need to pass this list to the UI layer so it shows to user list of fonts:

C++
        ...
        // converts the Webfont list to JSON string
        string json = JsonConvert.SerializeObject(_fontlist);

        // converts the JSON string to SciterValue
        SciterValue sv = SciterValue.FromJSONString(json);

        // calls UI layer TIScript function with the font data
        App.AppHost.InvokePost(() =>
        {
            App.AppHost.CallFunction("View_OnFontList", sv);
        });
    }).Start();
}

Here is where you find the usage of the “application events” interaction principle/idiom you can read more about here. It is pretty simple: application native layer simply calls scripting function to pass data to UI layer (host -> UI communication).

This is done with App.AppHost.CallFunction() method by passing the name of a global script function (or a namespaced one) and any number of SciterValue arguments. However, note that this code would be executing in a background Thread. That is dangerous and can cause data corruption because I am not sure TIScript is 100% thread safe.

The most safe thing is to switch back to UI thread and from there, do the call to scripting. This is why the usage of App.AppHost.InvokePost(() => { ... }) method will execute the lambda argument in the UI thread context.

SciterValue class is the one used to represent/create TIScript data in C#. For getting our SciterValue from a IList<WebFont>, we do a little tricky conversion. We first convert the IList<WebFont> to JSON string, and then use SciterValue.FromJSONString(json) to parse the JSON to a new SciterValue. It works very well!

GOOGLE 2 - Download and save GOOGLE font as .zip package. The "POST request" idiom (UI -> host -> UI)

The second task our GAPI C# class does is to download a given font family (which can contain more than one .ttf file), pack them in a .zip and save it in a given folder.

The method that handles it is pretty straightforward and self-explanatory, just read the sources. It's signature is:

C++
public static void DownloadFont(string family, string savefolder) { ... }

We pass the name of the font and the path where to save the .zip. We download the remote font files using a WebClient.DownloadFile() call, which does take time, so the DownloadFont() method should not be called from UI thread, but rather from a worker thread.

With the files downloaded to a temporary folder, .NET has a nice API for packing files from a folder to a .zip file, which happens by calling:

C#
ZipFile.CreateFromDirectory(tmppath, savefolder + "/" + family + ".zip");

(You must add a reference to System.IO.Compression.FileSystem assembly to use this API.)

Font download happens when user clicks the DOWNLOAD button in our HTML page, so we need a way to detect this UI event to trigger the font download. We shall not block UI while download is happening, and we need a way to tell UI when it is finished. For that, we use "POST request" idiom (UI -> host -> UI). Essentially in this idiom, UI asks native layer for a resource along with a callback script function that host should call when resource is available, so passing the result back to UI layer.

The code that does this is in file Host.cs:

C#
protected override bool OnScriptCall
(SciterElement se, string name, SciterValue[] args, out SciterValue result)
{
    result = null;
    switch(name)
    {
        case "Host_DownloadFont":
            string savefolder = args[0].Get("");
            string family = args[1].Get("");
            SciterValue async_cbk = args[2];

            Task.Run(() =>
            {
                bool res;
                try
                {
                    GAPI.DownloadFont(family, savefolder);
                    res = true;
                }
                catch(Exception)
                {
                    res = false;
                }

                if(async_cbk.IsUndefined())
                    return;// no callback provided

                App.AppHost.InvokePost(() =>
                {
                    async_cbk.Call(new SciterValue(res));
                });
            });

            return true;
    }
    return false;
}

In UI/HTML script code, we will invoke view.Host_DownloadFont(folder, family, function() { ... })when download button is pressed, which ends up in our OnScriptCall() C# handler of our Host class.

Quote:

In TIScript, whenever you call a method of the 'view' variable, and this function method is undefined, Sciter engine calls your OnScriptCall() event handler attached to SciterHost to give it a chance to handle the nonexistent function name. If you return 'true' from OnScriptCall handler, it means you handled the call, and script VM won't trigger a 'View (View([object View])) has no method - Host_DownloadFont' exception and stop executing script.

When script calls view.Host_DownloadFont(folder, family, function(res) {...}), we receive the given arguments in C# as a SciterValue array. From this array, we manually convert the first two items to string using args[0].Get(""), so we have the savefolder and family string variables (you can do such conversions for all basic C# types, the Get() method has overloads for int Get(0), bool Get(true), and so on..). We save the third SciterValue arg, the script callback, as a raw SciterValue variable 'async_cbk'.

Our handler then starts a Task which runs in a background thread the GAPI.DownloadFont() procedure. Note that it might fail since it depends on internet availability, so its surrounded in try/catch block. When DownloadFont() is done, we have a boolean result which we will pass back to UI layer as a sign if download occurred successfully.

As in the "application event" idiom, we need to switch back to UI thread for calling a script function by using App.AppHost.InvokePost(). Then we simply call the script callback using the Call() method of SciterValue passing the boolean result as a SciterValue too.

Now you know, SciterValue is a multi-purpouse class for dealing with any kind of TIScript data.

Backend Testing

Everything of our backend is in-place. From our App.Run() method, after our Host class calls SetupPage() to load HTML page, we must call:

C#
..
FontLister.Data.GAPI.Setup();
..

This method in GAPI class creates a thread that queries Google API for the fonts list, and then pass the list to UI layer by invoking the script global function "View_OnFontList" (see GOOGLE 1 section).

So for testing if it's all working fine, just put a BREAKPOINT at line 46 of file GAPI.cs which reads:

C++
..
App.AppHost.CallFunction("View_OnFontList", sv);// -> BREAKPOINT here
..

and start debugging our app. It should hit the breakpoint, else check for any early exception. So the test of Google API is just really pressing F5 to debug our app.

We need now test and debug our code in the way it would be called from our UI layer. As so, we will simulate the UI request directly from C#, so you learn how to test things without the frontend.

After this line 46, put the following line 47:

C++
App.AppHost.CallFunction("View_OnFontList", sv);// line 46
App.AppHost.EvalScript("view.Host_DownloadFont
(\"D:/\", \"Open Sans\", function(res) {})");// line 47

Should look like this:

Image 4

What the heck is it doing? view.Host_DownloadFont is a function that our HostEvh event handler class is natively handling (see GOOGLE 2 section), so when you call this function from TIScript, it ends up being handled by C# code.

What we are doing is emulating this script call from C# through EvalScript(). We pass as argument TIScript code to be executed in the global namespace of our SciterWindow.

What will occur is that it will invoke the Host_DownloadFont handler we defined in GOOGLE 2 section. Note that we are passing 3 arguments to Host_DownloadFont:

  • "D:/"
  • "Open Sans"
  • function(res) {}

Respectively: the directory where to save the .zip, the font-family, and an empty callback function.

Ok, so now you can Debug and see if everything is working fine. Put a BREAKPOINT in the line 47 we've just added, and a BREAKPOINT in file Host.cs, line 33. Press F5 and it should first hit line 47, press F5 again, and then it should hit line 33. Now you can step in the code until it reaches GAPI.DownloadFont(). After this call, wait a bit, and check if the file 'Open Sans.zip' was created at your D: drive. We've just emulated and tested what happens when user download a .zip for a given font-family. Remove line 47.

3. Hands on Code -> Frontend

Now we are entering an entirely new area of development which involves purely HTML/CSS and scripting with TIScript language. I might say it is easier than the backend, as long as you have a bit of experience with HTML and CSS. And even if you are an expert in Web technologies, it is worth reading this section because HTML/CSS support in Sciter has some subtleties which are not supported by standard browsers. TIScript has many difference from standard JS, but it still a JavaScript like language.

As we did with the backend, we can write and test the frontend completely detached from the backend, so not requiring a working backend.

HTML

This is all the HTML we need to replace as index.html content:

HTML
<html>
<head>
</head>

<body>
	<div .warning>Loading Google fonts..</div>
	<div #list />
</body>
</html>

It is standard HTML, until you notice some subtleties. Notice how we declared the <div> element:

HTML
<div #list />

There are two Sciter specific things here you must know. First, notice that we don't have an ending </div> tag. If the element contains no children, you can completely omit the closing tag, no matter what HTML tag it is. In browser, <div>s always need the closing tag.

Second, instead of declaring the element ID with an id="XYZ" attribute, as in <div id="list" />, we can simply say #THE_ID, as you would write an ID selector in CSS. You can do the same for the class attribute, so <div class="list list-colored" /> can be written as <div .list.list-colored />. Pretty clever method for writing the ID or classes of an element by using the same syntax from CSS. Sciter supports more attributes shortcuts, see here.

Also, in Sciter, you can use any tag name, for example <list-of-details></list-of-details>. But you also need to set it display CSS property, so the following is necessary to make list-of-details visible:

CSS
list-of-details
{
     display: block;// or inline, inline-block, ..
}

CSS

For styling our page, we need the following CSS <style> tag that you should put inside the <head> tag:

CSS
<style>
	body
	{
		margin: 0;
		padding: 10px;
		flow: vertical;
		background: #eee;
		font-family: system;
	}

	li
	{
		padding: 10px;
		margin-bottom: 10px;
		border: solid 1px #BBB;
		box-shadow: 0px 1px 1px #EEE;
		background: white;
	}

	h1 { margin: 0; margin-bottom: 10px; }

	b { font-size: 18px; }
	b.success { color: green; }
	b.error { color: red; }
</style>

There is not much to say, it is just standard CSS. The only Sciter specific thing here is the 'flow' property which you can read more about here. Essentially, it is the equivalent of CSS3 Flexbox layouting, but it has different naming and behavior.

The 'flex' property dictates how children of the element will be positioned inside it. The 'vertical' value we use says to layout element vertically, one below the other, in rows, no matter if it's inline or a block element.

TIScript

Scripting language is supposed to be a glue between native functions/components and UI events/elements. Also, one of the major purposes of script is to update state of the DOM, allowing you to add dynamics to your page.

TIScript separates different threading models. HTML, CSS and script (code behind UI) is based on assynchronous resource loading. Application core native layer consist of synchronous procedures for feeding up this assynchronous UI. So native layer is comfortable because it's not tied with UI and its event-based threading model.

For our app, here is all the script code you need to add to the <head> tag:

HTML
<script type="text/tiscript">
	function View_OnFontList(data)
	{
		$(.warning).remove();

		for(var item in data)
		{
			var el_item = self#list.$append(<li><h1>{item.family}
                          </h1><button>Download Font Family</button></li>);
			el_item.family = item.family;
		}
	}

	self.on("click", "button", function() {
		var el_btn = this;
		var folder = view.selectFolder();
		if(folder)
		{
			el_btn.state.disabled = true;
			el_btn.text = "Downloading..";

			var family = el_btn.$p(li).family;
			view.Host_DownloadFont(folder, family, function(res) {
				if(res)
					el_btn.$after(<b .success>Download completed with success!</b>);
				else
					el_btn.$after(<b .error>Error downloading!</b>);
				el_btn.remove();
			});
		}
	});
</script>

Note that TIScript code must go in between <script type=”text/tiscript”> tags, and not simply <script> tags.

I will not explain the code because it's out of the scope of this article. You can learn more about TIScript here and a see a comparison char to JS. But it can be easily be understood by JS developers just by reading it. However, notice the following differences from normal JavaScript:

  • strings literals are only allowed with double quotation marks: "My string" is valid TIScript string, 'My string' is invalid syntax;
  • function/methods where name starts with $ are stringizer functions:
    • the parameter is interpreted literally as a single string, characters ‘(‘ and ‘)’ are serving role of quotes by themselves.
    • its normally used in functions receiving a HTML parameter as in: $append(<div>)
    • or functions receiving CSS selectors: $(div > span)
    • here you notice the existence of a $ global function, as in JQuery, for selecting DOM elements, but having a slightly better syntax, without the need to enclose the string argument in quotation marks, clever don't you think!?
  • self#list expression is a shorthand syntax to $(#list), self is always a reference to the root element of the page, the <html> element

Frontend Testing with sciter.exe

In modern browsers, we have the F12 tools that allow us to inspect the DOM of a loaded page and even debug running JavaScript code. Sciter equivalent to this is the sciter.exe tool, as I said before.

To test our frontend, open the index.html file in sciter.exe by drag-n-drop the file from Windows explorer inside it. You will see a forever standing message of 'Loading Google fonts..'. Since we don't have the backend, it will never disappear. But thanks to scripting, we can easily emulate the data that flows between these 2 layers.

The following code is enough to emulate the call to View_OnFontList() that would otherwise be called by our missing C# native backend:

JavaScript
View_OnFontList([
    { family: "Helvetica" },
    { family: "Open Sans" },
    { family: "Consola" }
]);

Add this code to the end of your <script> tag, go back to sciter.exe, and press the reload button. And that is it. It should display a list with 3 fonts. You can inspect the DOM by clicking the 'cog' button, which will show the following window:

Image 5

We can easily see our DOM tree with 3 <li> elements representing each font in the data given to View_OnFontList().

Test/Debugging Sciter Applications + Remote Inspecting HTML

The sample app code is complete now, so we want to test and debug/run the complete executable.

If necessary, download the complete sample app before trying this section.

While debugging in VS, make sure to have the Output window open because Sciter prints messages to stdout, which might range from simple warnings, but more importantly, TIScript execution errors and unrecognized CSS rules (those are the most common errors to happen from my experience).

But for it to work in C# debugger, make sure to enable native debugging so you will see Sciter error messages in the Output window: Project Properties / Debug / Check 'Enable native code debugging'.

Press F5 and our Google font downloader app should run smoothly.

In normal browser, we can inspect the DOM of your lively running HTML page (F12 tools). We can to it in Sciter too by remotely inspecting our running C# process through the inspector.exe tool.

SciterSharp offers an easy method do to this (you may want to read about the mechanism involved here).

First, you need to run inspector.exe contained in Sciter SDK (/bin/64/inspector.exe).

Then, in C#, you simply call the DebugInspect() method of SciterHost class:

C++
// Prepares SciterHost and then load the page
var host = new Host();
host.SetupWindow(wnd);
host.AttachEvh(new HostEvh());
host.SetupPage("index.html");
host.DebugInspect();// >-- call it after SetupPage(), dont forget to run inspector.exe
..

And it will open Sciter inspector. With it open, in your program, you CTRL + SHIFT + Click any element for selecting it in the DOM tree, as is shown below:

Image 6

And the best thing, it works in Linux too, for that you need to add 'inspector64' from SDK along the executable binary.

4. Compiling and Running on Linux

First, you need to install Mono and MonoDevelop in your Linux system, so check the instructions here.

Then, just open the Solution in MonoDevelop, right-click the FontListerGTK project, click 'Set As Startup Project' and try to build. Hopefully, it will compile as is.

Now if you try to run, it will probably throw a 'TypeInitializationException' with a inner 'DllNotFoundException: sciter-gtk-64.so'. That's because Mono can't find Sciter DLL (.so). You need to install to your Linux environment. The easiest way is to download this script (sample .zip already contains this file at root folder) and then just:

C++
sudo bash install-libsciter.sh

Another issue I faced in Linux was that Google Webfonts API was throwing an Exception. Hopefully, I found a workaround here, it saved my day:

Image 7

Appendix: Sciter Tricks

Global Configurations

These are some global configurations that you can set for Sciter engine from C# code:

SciterX.API.SciterSetOption() API provides many options. One I use sometimes is change the SCITER_SET_GFX_LAYER option, which lets you set the GFX backend (Direct2D by default on Windows). Why? Because in old computers, engine often crashes due to old display hardware/drivers, so what you can do is switch to non-accelerated GFX mode, setting this option to GFX_LAYER_WARP, which is CPU based image rendering:

C#
SciterX.API.SciterSetOption(IntPtr.Zero, SciterXDef.SCITER_RT_OPTIONS.SCITER_SET_GFX_LAYER,
                            new IntPtr((int) SciterXDef.GFX_LAYER.GFX_LAYER_WARP));
  • Another API lets you append CSS content to all HTML pages the engine creates. Note that many script functions create new HTML pages, like view.msgbox(), and you don't have direct control over the styling of those page, so this API might be useful.

To set the background of all pages to silver, you would call:

C#
string css = "html { background: silver; }";
byte[] bytes = Encoding.UTF8.GetBytes(css);
SciterX.API.SciterAppendMasterCSS(bytes, (uint) bytes.Length);

Native C# DOM Manipulation

In this article, we haven't used it, but SciterSharp library has a full API for manipulating the page DOM through the SciterElement class. Similarly to SciterValue, SciterElement is a multi-purpouse class for any kind of DOM manipulation: creating elements, traversing the DOM tree, changing element state/attribute/CSS, and so on...

Here is a simple example of how to append a <h1> header to a page and set its CSS color to blue.

C#
// get the page <body>
var se_body = wnd.RootElement.SelectFirst("body");

// append a <h1> header to it
se_body.TransformHTML("<h1>Wow, this header was created natively!</h1>",
                        SciterXDom.SET_ELEMENT_HTML.SIH_INSERT_AT_START);

// set <h1> color to blue
se_body[0].SetStyle("color", "#00F");

TIScript: View global data/communication

It is desirable sometimes to have a way to share data between different HTML pages, for example, distinct <frame>'s pages, or even do communication between multiple Sciter windows. It is possible because Sciter windows in the same process share the same TIScript VM.

You do this with 'View' class properties. The following assignment:

JavaScript
View.CreateAlert = function() {
     self.$append(<div .alert />)
}

..makes the View.CreateAlert() available globally, so you can call it from any page.

You could also want to share data instead:

JavaScript
View.standardAlerts = {
     success: "You did it, I knew you could!"
     warning: "Hey, you were advised",
     error: "Huston, we have a problem!"
};

Resource Loading: Injecting TIScript Content in Every Page through 'sciter:debug-peer.tis'

This is a very useful trick. This inject TIScript content to every HTML page engine loads.

First, you need to make sure that when you create the SciterWindow, you pass the SciterXDef.SCITER_CREATE_WINDOW_FLAGS.SW_ENABLE_DEBUG to the CreateMainWindow(). Note that if you call it just by passing the size of the window, by default, it will get this flag anyway, so the following call is OK:

C++
wnd.CreateMainWindow(800, 600);

SW_ENABLE_DEBUG enables you to remotely inspect your page using inspector.exe tool. In order to do it, engine injects the file 'sciter:debug-peer.tis' to all pages loaded. We can cheat the engine by customizing the content of 'sciter:debug-peer.tis' file via handling of the SC_LOAD_DATA notification, so it will load our given TIScript content instead. To do this, in your SciterHost derived class, add the following to the OnLoadData() overridden method:

C#
if(sld.uri=="sciter:debug-peer.tis")
{
    string TIScript_content = "function Meow() { stdout.println(1234); }";
    byte[] buffer = Encoding.UTF8.GetBytes(TIScript_content);
    _api.SciterDataReady(_wnd._hwnd, sld.uri, buffer, (uint)buffer.Length);
}

This would make function Meow() available globally in all pages. Note that you can add any TIScript code, so you could for example, add functional code that changes the page DOM, or check the loaded page URL with self.url() and do some kind of custom procedure.

Note, that are alternative methods for doing this which are described here.

Cools Things You Can Do in Sciter

The possibilities are endless, here I will just point some links of things that you can achieve in Sciter:

  • The main source of inspiration of what you can achieve with the engine in inside the samples/ folder of the SDK, it is a MUST to check out!
  • You can write HTML like in PHP <?php echo "whatever"; ?> where you can mix output programmability in the middle of the HTML, see http://sciter.com/forums/topic/new-page-load-using-tiscript/#post-43481
  • Through script, you can customize/override the drawing of any DOM element in all of its 4 painting steps: background, content, foreground and outline, see http://sciter.com/342-2/

Epilogue

This ended up being a very long article, even though it covers the essentials for understanding all the pieces involved in a C# Sciter based desktop app. Hope it was a interesting reading.

My next major goal is to complete SciterSharp support for OSX with a proper tutorial in how to use it in this platform. So stay tuned!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
MI Software
Brazil Brazil
Ramon has a bachelor in Information Systems at University of Caxias do Sul. He started his career in the creative area, working with web design, and then evolved to work with a more hardcore area of control systems engineering while making C#/.NET systems to automate every kind of process. This was when he discovered his passion for the low-level world, working with C, C++ and D development.
Check my things at http://misoftware.com.br/

Comments and Discussions

 
QuestionIs Sciter based on WebKit? Pin
aloneguid10-Dec-20 5:00
aloneguid10-Dec-20 5:00 
GeneralSciter desktop application access Windows resourses Pin
Gaurav Upadhyaay2-Dec-19 22:19
Gaurav Upadhyaay2-Dec-19 22:19 
QuestionNot able to access html elements after view.Host_handler call Pin
Member 1431505417-Apr-19 23:35
Member 1431505417-Apr-19 23:35 
QuestionWebpage images don't load Pin
Brett Goodman3-Feb-19 0:56
Brett Goodman3-Feb-19 0:56 
AnswerRe: Webpage images don't load Pin
Member 159190869-Feb-23 2:43
Member 159190869-Feb-23 2:43 
QuestionIntegration with OpenTK Pin
Stylianos Polychroniadis15-Jan-19 3:28
Stylianos Polychroniadis15-Jan-19 3:28 
AnswerRe: Integration with OpenTK Pin
Ramon F. Mendes17-Jan-19 10:13
Ramon F. Mendes17-Jan-19 10:13 
GeneralRe: Integration with OpenTK Pin
Stylianos Polychroniadis17-Jan-19 10:46
Stylianos Polychroniadis17-Jan-19 10:46 
AnswerRe: Integration with OpenTK Pin
c-smile17-Jan-19 10:43
c-smile17-Jan-19 10:43 
GeneralRe: Integration with OpenTK Pin
Stylianos Polychroniadis17-Jan-19 10:51
Stylianos Polychroniadis17-Jan-19 10:51 
QuestionVery nice but ... Pin
df49414-Jan-19 23:24
df49414-Jan-19 23:24 
AnswerRe: Very nice but ... Pin
Ramon F. Mendes18-Jan-19 6:56
Ramon F. Mendes18-Jan-19 6:56 
QuestionThe GitHub project link is incorrect Pin
Ivaylo Slavov13-Jan-19 10:48
Ivaylo Slavov13-Jan-19 10:48 
AnswerRe: The GitHub project link is incorrect Pin
Ramon F. Mendes17-Jan-19 10:06
Ramon F. Mendes17-Jan-19 10:06 
GeneralMy vote of 5 Pin
Stylianos Polychroniadis12-Jan-19 11:27
Stylianos Polychroniadis12-Jan-19 11:27 
QuestionBroken links Pin
PabloAVeliz6-Dec-18 9:23
PabloAVeliz6-Dec-18 9:23 
AnswerRe: Broken links Pin
Ramon F. Mendes11-Jan-19 7:53
Ramon F. Mendes11-Jan-19 7:53 
QuestionCompatibility of Sciter with .Net framework 3.5 Pin
PabloAVeliz6-Dec-18 7:31
PabloAVeliz6-Dec-18 7:31 
AnswerRe: Compatibility of Sciter with .Net framework 3.5 Pin
Ramon F. Mendes10-Jan-19 6:34
Ramon F. Mendes10-Jan-19 6:34 
Hi Pablo

I never tested it with .NET 3.5
You may want to download the SciterSharp source-code from GitHub - ramon-mendes/SciterSharp: C# bindings for Sciter - create HTML/CSS/TIScript based native apps[^] and try to compile it for .NET 3.5
QuestionGreat article ,thanks for sharing Pin
ibrahim ragab25-May-18 19:25
ibrahim ragab25-May-18 19:25 
QuestionWhy this approach over Xamarin and C#? PinPopular
MSBassSinger12-Sep-17 7:10
professionalMSBassSinger12-Sep-17 7:10 
AnswerRe: Why this approach over Xamarin and C#? Pin
Ramon F. Mendes16-Jan-18 8:06
Ramon F. Mendes16-Jan-18 8:06 
GeneralRe: Why this approach over Xamarin and C#? Pin
MSBassSinger11-Jan-19 6:59
professionalMSBassSinger11-Jan-19 6:59 
GeneralRe: Why this approach over Xamarin and C#? Pin
Ramon F. Mendes11-Jan-19 7:56
Ramon F. Mendes11-Jan-19 7:56 
GeneralRe: Why this approach over Xamarin and C#? Pin
MSBassSinger11-Jan-19 8:44
professionalMSBassSinger11-Jan-19 8:44 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.