15 July: Werewolf publishes his results already...

Late entry Werewolf published his solution for the GUI to a command line utility only after a couple of days work.

You can download his solution too. In the .rar you can find a readme.doc to get you on your way.

The documentation on this project is available as a Word document. The contents is published below also.

Note from self: if you start the application and want to pick an example, pick one from the Examples folder. The .cfg files in the root of the application folder are for internal use only.

GUI to a command line program in 2 days

By Werewolf

This article describes how Delphi helped me to build this tool in less than 2 days

Day 1

Thoughts

I've received message about start of Darwin Race of Languages. Problems described on site where not so hard to code. I've been using Borland Delphi since 2001 so many things where common to me.

I've decided to work on "GUI to a command line program" because it is simple one problem.

Requirements for this little app mentioned here where simple to but I wanted to create more complex program that can support such things:

  • Support for different number of task to be run sequentially
  • Support to save and load whole tasks for later reuse
  • Support for different types of arguments (file names, password, decimals etc.)
  • Support for Linux

What I've already had…

Delphi 6 can create CLX (cross-platform) applications that meant that they can be compiled with Kylix to produce same functionality on Linux based systems.

Conditional defines in source separate platform dependent code.

All cross-platform stuff is implemented in Borland interface for Qt library, so you don't need to worry about portability. It's already done for you for most common cases.

Also CLX Application class in Delphi have DefaultStyle property that represents a basic "look and feel" for the widgets in the application. This helps to create unusual interface, like skins in some programs.

There where situations in my experience when I had to create different editors for different data types. By the way Delphi already has components for the most common cases.

Save and load in Delphi is simple as calling SaveToFile or LoadFromFile but to be more portable I've decided to use ini-file format so can always check data written by program. Delphi have TMemIniFile class for this that supports CLX.

If you do not want to collect output info running tasks on Windows platform is simple as API call of ShellExecute in other case pipes would do the job.

I'm not regular Linux coder so I've decided that creating *.sh script would be enough for requirements on Linux based systems.

The beginning

After opening Delphi IDE I've clicked File - New - CLX Application. IDE created project file and added form to it. Now I already have CLX application ready to run.

From Component Palette you can drag any components to your form and before saving Delphi will add units required for those components to uses clause. So it's not necessary to remember where the component was defined.

Delphi designer has object inspector where you can assign initial properties for components and set event handlers. It also has editors for special property types like file names, list of strings, list of images etc.

I prefer to use TActionList component in most cases to provide program functionality. GUI components (like menu items, toolbar buttons, buttons etc) can be linked to actions on fly deriving necessary properties.

So I put menu, toolbar, action list, image list to my form.

Also I put TTabControl to represent different tasks as tabs, TTreeView to show list of available arguments, some Labels, edits and a button.

So I've created basic visual interface for my program. That was 2 minutes of work =)

On this stage it's only draft that will be polished later.

Coding

I've added actions that represent file functionality like saving, opening, creating.

When I write code I compile it periodically to reduce common code style mistakes like forgotten semicolons, required operators etc.

When part of functionality is done I run program to test if it works as expected. Delphi has powerful debugging possibilities. So after all I get program that works =)

I've created additional unit to contain common classes for my program:

  • TCMGUIConfig that can be saved and loaded from configuration file, contain set of programs to be run
  • TProgramItem represent task to be run containing all available for use arguments.

Implementing first one was easy because Delphi class TMemIniFile can handle different types of data to store and load.

TCMGUIConfig also implements storing and loading program item that it contains.

IDE Features

Implementation of methods and properties of class is simplified with use of code completion feature. So you can write 'property SomeName:string;' then press Ctrl+Shift+C - IDE will create Field for that property and 'Set' method for it.

The use of Code insight feature speeds up coding inserting code templates for you. You can write 'ifb' and press Ctrl+J IDE will insert such template:

if then

begin

end;

positioning cursor between 'if' and 'then'.

Continue Coding

After I was done with object classes, I need to map their properties to GUI editors

This was done by implementing LoadConfig and SaveConfig methods on main form.

Loading and saving available argument of program was implemented like LoadFromStream and SaveToStream methods of TTreeStrings but modified for use of TStrings.

This ends base file I/O functionality.

Changing GUI interface appearance was implemented by setting Application.Style.DefaultStyle to enumeration item corresponding GUI control selected.

Flags Editor

To display and edit argument properties I created new form.

This form has class function that creates its instance loads current argument and shows up for editing.

For different types of data I've created enumeration in objects unit

type

TTypeID = (tid_Group, tid_None, tid_Char, tid_String, tid_File, tid_Int,

tid_Float, tid_Password);

and written some helper functions:

procedure GetSupportedTypeNames(ToList: TStrings);

function TypeIDString(t: TTypeID): string;

function StringIsTypeID(const s: string; const t: TTypeID): boolean;

'Types' Combo box items represent this enumeration members.

For different types of data editor changes to corresponding

For example:

  • for int data type editor is spin edit
  • for file data type editor is edit with button for dialogue
  • etc.

Different types of editor require different methods of loading and saving value this implemented in SetValueByType and GetValueByType methods.

Use of enumeration in this case simplifies creation of new data types with a minimum of code changes.

Day 2

Coding Process Execution

You must know APIs for process creation to complete this functionality.

Windows platform

You can use ShellExecute from ShellAPI unit and CreateProcess from Windows unit. When you grab output it can be OEM dependent so you must convert it to ANSII.

Internal program execution is implemented in ExecConsoleApp procedure that writes StdOut and StdError handles output to TStrings.

Execution in system console just creates batch file and runs it.

Drag & Drop

VCL Style

You can use drag &drop operations to move available arguments in tree or to copy same arguments with Ctrl pressed while dragging.

VCL Style of drag & drop concludes Delphi component based Event Handling - creating handlers for OnDragDrop and OnDragOver events of control.

You can assign handlers using IDE Object Inspector.

QT Style

QT Style of drag & drop concludes overriding of common Delphi handling for using power of QT Drag & Drop where you can get mime type of data dropped from other application.

In this program you can drag configuration file from explorer to open it or executable to Program File Editor to set new value.

Polishing

When all functionality is done you can begin with visual design.

In Delphi there is an image list component that can contain pictograms for actions (& menu items & buttons linked to them), you can load bitmaps and icons directly from this component.

I've chosen some bitmaps with IDE image list editor from GlyFX common library and modified some of to represent functionality "on guess".

If some one wants to load different (skinned) bitmaps he can load bitmaps from file(s) to image list at runtime or use several image list components.

Action list component in Delphi have OnUpdateEvent where you can enable or disable actions (& menu items & buttons linked to them) depending on any conditions. So you can allow or disallow user to do something =)

Localization

You can translate this project to different languages using Delphi Translation Manager that can create additional language resource DLLs

So in code it's better to use resource strings if you want to localize them later.

Delphi Translation Manager can use repository to store previous translations and reuse them.