Building a Logic Layer for the Description of Wizards

Thursday Aug 25th 2005 by Michael Kanzieper

Learn to construct and use the logical layer for the description of wizards.

Figure1: The panel of the test application


While writing a program for the step-by-step editing and keeping of a complex document, I had an idea that allowed me to solve this problem in a simple and efficient way.

A bit later, I realized that the way I solved my particular problem represents quite a general approach to a much larger class of problems and hence can be useful to somebody else. That is why I decided to write a short article to share the idea. This could be especially appropriate given that there are not many papers addressing the art of programming.

I would call a class of problems to be dealt with wizards. In a sense, any installation process is a simple example of a wizard. A user is asked to go through a series of dialogues, choose relevant options, and run the installation procedure. Schematically, this chain of events can be depicted as the following diagram:

Figure 2: The scheme of an installation process

A wizard allows a user to jump one or more steps back to repeat them again if needed.

In essence, many more processes exist that could be called wizards and represented by a scheme similar to that in Figure 2. To name a few, I mention the processes of (i) filling the forms and performing complex transactions in data management, (ii) testing and setting up numerous devices, (iii) calibrating measurement devices. This list is fairly incomplete, and readers may easily continue it by adding examples of their own.

In a more general way, a wizard-type problem can be formulated as follows: A set of task exists; the tasks are performed in a certain order that is determined by some logic considerations dictated by a particular application.

Importantly, a wizard differs from the usual stream diagram. The engine of a stream diagram verifies whether a task can be performed; it runs a task automatically only if a task can be executed. For instance, if a task is some function, the function will be executed once the function parameters are made available.

A wizard has a peculiar feature: The tasks are not automatically executed; they are run manually by the application user while the engine of a wizard simply detects what tasks are available for running at each time instance and allows the user to run them. However, it is the user who eventually decides what tasks will be performed and when.

Consider a process depicted in Figure 3. It consists of five tasks. Notice that tasks 1 to 3 do not depend on other tasks and can therefore be executed in any order at any time. Task 4 may only be done after both Tasks 1 and 2 are completed. Task 5 may be run once Tasks 3 and 4 are completed.

Figure 3: Process represented as a flow chart

If, at this time, one has to run Task 2, both Tasks 4 and 5 can be performed anew as well. An important remark is in order: If a task was performed anew and other tasks whose results depend on this task exist, these other tasks have to be run anew as well. Such a problem is also of a wizard type, albeit it is a bit more complicated. At each step, a user is interested in two issues:

  1. What task can (or should) be done?
  2. What tasks are yet to be performed?

A programmer's problem is somewhat more complicated: He or she should know how such logic could be implemented. As a rule, everything is done in a traditional manner. At each step, one analyses the current state of the process and makes a decision. For instance:

if (IsDone(Task1) && IsDone(Task2) && IsDone(Task3)
                  && IsDone(Task4) && !IsDone(Task5))

In case that the number of tasks is large, and the links between them are complicated, the presented solution becomes hopelessly cumbersome. In addition, a tiny change in logic would lead to the need of making significant alterations of the code. Its debugging is a problem in its own right.

Is there any way to make your life easier? I claim that there is a way that saves the time. I will explain it in the following section. Let me just notice that my solution is by no means the only solution.

Computational Model

Representation of the task via diagrams similar to the one in Figure 3 is a starting point for building a computational model. Any task can be put represented by a functional block; see Figure 4.

Figure 4: Model of a task

The box representing the task has two major attributes:

  • List of input conditions
  • List of output conditions

The input conditions are given by a vector of boolean variables. A task can be performed if all the variables are set to true.

Obviously, this blocks certain functionality: A certain action is attached to each task (transaction, calculation, input/output act, and so on). However, at this point, you are interested in the fact of performing or not performing a task rather than in its concrete sense.

Once a task is performed, a vector of output variables is set to true.

It should be mentioned that input and output conditions set the links between the tasks. The above model allows one to build an automatic solver that would be able to answer, at each stage of the process, two questions formulated earlier: 1) What task/tasks can (or should) be done? and 2) What tasks are yet to be performed?

Implementation of the Model

A system is implemented in C#. The only for choosing this language is that it is a current environment at my workplace. One could equally use Java, C/C++, DELPHI, and so forth. A solution is attached to article. Below, I explain the major points in the implementation:

1. Task

The class Task corresponds to the notion task. This class implements the ITask interface. The most important elements of this interface are:

  • The Done method; it imitates a procedure for performing a task

  • and

  • The IsDone property; it allows one to verify whether the task was or was not done.

The input parameters of a constructor of the Task class are: task's identifier, lists of input (radarIsReady, operatorIsReady), and output (detectionIsDone) conditions (the Condition object will be described in the next section):

ITask objectDetection
   = new Task("ObjectDetection",
     new Conditions(detectionIsDone),
     new Conditions(radarIsReady,operatorIsReady))

A set of objects of the Task type comprises a system. This system is implemented by means of the object with the IEnvironment interface.

The Done function realizes the following operations:

  1. It sets the output conditions to true.
  2. It sets the IsDone property to true.
  3. It modifies a system in accordance with changes in output (Reset).

2. Conditions

As was explained before, a condition is a normal boolean variable. Here, the variable is described by the ICondition interface and is implemented in the Condition class. A user should simply create this object by using a constructor:

ICondition condition = new Condition();

3. Environment

Environment is a collection of objects of the Task type. The major elements of the Environment object are:

  • A Ready method that provides a list of tasks that are ready to be run.
  • A Reset method that refreshes a system state. A user calls this method indirectly, through the Done operation of the object Task (refer to Task 1).

4. Major operations

A user should perform three major actions:

  • Describe the system
  • Run the task
  • Check the system state

The last two steps can be repeated indefinitely.

5. Description of system

A system represented in Figure 3 is described by the following procedure:

   postConditionTask1 = new Condition();
   postConditionTask2 = new Condition();
   postConditionTask3 = new Condition();
   postConditionTask4 = new Condition()

   Task1 = new Task("Task1",
                    new Conditions(postConditionTask1),
                    new Conditions());
   Task2 = new Task("Task2",
                    new Conditions(postConditionTask2),
                    new Conditions());
   Task3 = new Task("Task3",
                    new Conditions(postConditionTask3),
                    new Conditions());
   Task4 = new Task("Task4",
                    new Conditions(postConditionTask4),
                    new Conditions(postConditionTask1,
Task5 = new Task("Task5",
                 new Conditions(),
                 new Conditions (postConditionTask3,
   environment = new Environment(
                 new Tasks (Task1,Task2,Task3,Task4,Task5))

6. Running the task

To run the task, one has to call the Done method for a particular Task; for example:


7. Verification of the system's state

Use the IEnvironment::Ready method.

In particular, having created a system and run the Ready procedure, one can check that Tasks 1 and 2 (see Task 5) are ready for running.

By means of the IsDone property, one can check that neither of the tasks was done.

ITasks list = environment.Ready()
Console.WriteLine("Task1 is ready to execute : {0}",
Console.WriteLine("Task2 is ready to execute : {0}",
Console.WriteLine("Task1 is done : {0}",Task1.IsDone);
Console.WriteLine("Task2 is done : {0}",Task2.IsDone);
Console.WriteLine("Task3 is done : {0}",Task3.IsDone);
Console.WriteLine("Task4 is done : {0}",Task4.IsDone);
Console.WriteLine("Task5 is done : {0}",Task5.IsDone)


Direct and indirect cycles are forbidden, as shown in Figure 5:


Figure 5: Example of direct (d) and indirect (id) cycles

Note: The above application assumes that a scheme contains neither direct nor indirect cycles.

Test Application

The test application describes the process represented by Figure 6.


Figure 6: A scheme of the process being modeled in the test application

For illustration purposes, each button corresponds to a certain task. Execution of a task is imitated by a button click. An adjacent checkBox indicates a state of a task: performed/not performed, refer to Figure 1.

Brief Description of the Project

The solution accompanying this article consists of four projects.

The Environment project contains all interfaces and the classes implementing them. This is just a core of the solution.

The TestConsole project illustrates a simple way to create a scheme of the project and to run some of the wizard's commands.

The LogicLayer project describes a diagram depicted in Figure 6.

The TestWinApplication project is a GUI application. It illustrates how a wizard functions, making use of minimal graphic tools. Refer to Figure 1.


The application was written about a year ago in just a few days. I decided that it can be of interest for a wide audience, and hence decided to submit this short article.

About Michael-K(anzieper)


Michael Kanzieper,
M.Sc. in System Engineering, from Tallinn Technical University, Estonia. Currently, I work in a hi-tech enterprise in Israel.

Mobile Site | Full Site
Copyright 2017 © QuinStreet Inc. All Rights Reserved