Introduction
I have built a flexible user interface which allows the user to shape the user interface according to physical process and to represent the real process. In this way, the user immediately sees what the effect of a control action is.
In this sample application, a signal tower to control model trains is used as an example. By replacing the SVG (Scalable Vector graphics) in the resource files, this application can easily be modified to control a chemical process flow for an example.
Editmode: Building the visualisation:
After building the visualisation, it can be controlled in Control Mode.
And to show the status of a tile, click the occupation test in the menu, each tile will show it's occupied:
Of course, this should be triggered by the process/hardware.
Background
The main objective of this article is not software design primarily, but more focused on the concept and architectural design. Many designers will fall in the pitfall and start with a design based on the graph theory. And what this design tries to show is that a UI control itself is not bound to other controls like in the physical world, but only by the graphical representation (location on the screen) and therefore the graph theory is not needed. This reduces the complexity of UI design. Of course, the model (outside of this articles scope) still contains the relational knowledge and uses the graph theory.
Using the Code
Two main patterns are used throughout the complete application:
- MVP, Model View Presenter pattern to loosely couple UI with the other layers (and please notice that this sample only contains the view part)
- Proxy pattern to let the
Layout
and AddTile
class access the tile library via the MainApp
.
And two events are used:
ControlStateChange
, to notify the view (tile representation) about an external state change or to notify the external hardware about a state change triggered by a user (tile clicked).
(switch
is thrown or straight / valve
is open or closed) TrackStateChange
, to notify the view about an external state change. (track is free or occupied / water pipe is empty or flooded)
To identify the events, two types of IDs are used throughout the application (all layers):
ControlID
s TrackID
s
Examples:
ControlID
: “CI10
” will be translated to IODevice x, register 8 and bit 6. If the user clicks on the tile, an event will be sent to the IO Service to toggle the IO bit. Likewise, if an external event toggles the IO bit, the IO Service will notify the View about this event and the view will update the Tile
.
TrackID
s can’t be triggered by a user but only by the IO Service. The IO Service will notify the View
whether a track is occupied or free and the view will update the Tile
.
The complete application looks like this:
The scope of the sample application looks like this:
The view part of this application contains the following classes:
MainApp
This is the main application and acts as a boiler plate for user actions (menu actions), shows/hides panels and translates application strings by using two resource files (Dutch and English).
TileProperties
This is a user control which allows the user to change 3 properties from a control:
- Logical name (e.g., Junction 21)
ControlID
, a hardware id to relate a bi-directional hardware event with a tile TrackID
, a hardware id to relate an input hardware event (track is occupied) with a tile
The IO service layer will use both IDs to address certain hardware (logical to physical translation) and handle hardware events by notifying the view (which updates the tile on its turn).
AddTile
This is a user control which allows the user to select a tile from a library. In this sample application, two kinds of tiles are supported in this sample:
- Tracks
- Signals
Signals only support control ids (run/stop, red/green) and Tracks support control ids (left/right, open/close) and track ids (Occupied/Free, Flooded/dry).
Layout
This class is derived from a panel and handles the CRUD actions related to tiles and the movement/placement of tiles on a canvas (Mouse handling, single/multiselect).
And this class is the major interface with the MVP pattern. In the full application, this class creates the presenter and model. Also loading from the model is supported by this class.
Tile
The Tile
class is the graphical representation of a tile (the physical tile is handled by the model). Based on its state, it will show the related bitmap in the right orientation.
SVG Tile Library
To support a flexible interface and to make it possible to quickly extend and change the functionality, a SVG parser has been created. This parser reads SVG images related to a tile from a resource XML file (library) and delivers the file as a bitmap set.
Example:
A simple switch (left/right) has 4 states (bitmaps):
- Left (control state) and free (trackstate)
- Left (control state) and occupied (trackstate)
- right (control state) and free (trackstate)
- right (control state) and occupied (trackstate)
The Tilefactory
class discloses the resources files as libraries and supplies the following (ITilefactory
):
List<String> GetCategoryList();
List<String> GetAvailableTiles(String Category);
Dictionary<int, Bitmap> GetBitmap(String Category, String TileName, Color BackgroundColor);
GetCategoryList
: This function delivers a string
per resource file. In this case: “Tracks
” and “Signals
” GetAvailableTiles
: This function delivers a string
per tile depending on the category: “buffer
”, ”curve
” , ”straight
”, etc. when track
is selected as category
. GetBitmap
: This function delivers a set of bitmaps as described above and uses the category
and tilename
as selector and Backgroundcolor
as color for the background.
To deliver these bitmaps, the following classes are used:
SVGItem
(abstract base class) SVGPath
SVGRect
SVGCircle
Each derived class used a SVG string
from the SVG XML files.
So SVG Path is created when a SVG file contains the “Path
” line. Then this class will be able to parse this line and draw it in a bitmap. Currently, only Path
(relative and absolute), Rect
and Circle
are supported. And the rotation translation is only supported for Rect
. (It can easily be ported to the other types.)
Points of Interest
I needed a lightweight SVG parser and have built one myself. I do know some libraries are available, but since I wanted to learn from this application, I decided to build one myself. It currently supports only a limited set of SVG functionality, but it fitted my needs.
History
- 2nd September, 2014: Initial version