Click here to Skip to main content
15,892,298 members
Articles / Desktop Programming / Windows Forms

Tripous introduction

Rate me:
Please Sign up or sign in to vote.
4.45/5 (11 votes)
26 Jun 2010Eclipse35 min read 36.3K   833   32   9
An Open Source desktop database (data entry) application framework.

Introduction

Tripous is an Open Source application framework, written in C#, for rapid implementation of WinForms data entry applications.

The official site can be found at http://tripous-net.com. The project page at SourceFourge can be found at http://sourceforge.net/projects/tripous.

To use Tripous, you need, at least, .NET 3.5 with Service Pack 1 and Visual Studio 2008.

This article is an introductory tutorial on how to code a basic Tripous application. You may use this sample application as a base for writing your own Tripous application with its own data and forms.

What Tripous offers

  • Rapid application development.
  • Unified data access; that is, database server neutral SQL statements and, the most important, database server neutral SQL parameters.
  • A powerful and flexible business class, his majesty the Broker, fully declarative, or inheritable, or both.
  • Base data entry form classes and its own user interface controls that can be used to accelerate user interface construction.
  • User access control, easily (really) extensible to cover any type of object.
  • Automatic logging and exception handling.
  • XML serialization (Tripous does heavy use of XML).
  • Automatic SQL statement generation.
  • Unified access to application resources.
  • Pluggable application options subsystem.
  • Plug-ins.
  • Designable reports (using an external report library of your choice).
  • A TCP server and client that communicate using simple XML commands (poor man's SOAP), fully pluggable, and extensible.
  • SQLite integration.
  • PDA framework with all the goodies of the desktop framework (controls included).
  • ...and more.

Tripous tutorials

I have already written some tutorials on how to use Tripous sub-systems and I plan to write more. You can find those tutorials at my profile page here in CodeProject.

What name is this?

Tripous is an ancient Greek word meaning a three-legged stand. Tripod is a synonym. Delphic Pythia was sitting on a tripous when she was making her oracles. Talking to a database server is similar to talking to an oracle. You issue a query, but you have no idea what you are going to get back as an answer. And since the Tripous framework is about data, I find Tripous is a suitable name.

Why a framework?

Tripous is a framework for rapid development of data entry applications.

A framework is a code base that provides services in building applications. .NET is such a framework. Most programming environments deserve that classification too. So why bother to build such a framework? Isn't .NET and all those .NET classes enough? Well, yes they are.

A framework is required:

  • if you believe you have to hide the various database connection technologies behind a unified data access layer.
  • if you're going to provide base business logic ancestor classes.
  • if you think your data entry forms should have some level of uniformity.

Background

Tripous is a traditional system and has its own perspective regarding many aspects when building an application. Let's examine the most important areas.

P/Invoke

No P/Invoke. Tripous vanity is to be, some day, a drifter in the Mono world. And in the DotGNU continent. So it uses P/Invoke only when there is no other solution. For instance, it uses P/Invoke to call the Remote API (RAPI). P/Invoke marries the framework to a certain system. And that's not a good habit for a framework. A friendship is more than enough.

Tripous is not ORM

Tripous' main task is to handle data stored in relational databases. So we can say that Tripous is mainly a database application framework.

There are two types of database application frameworks: let's name them traditional frameworks, and the Object Relational Mapping Frameworks (ORM) or Object Persistent Frameworks (OPF).

In an ORM/OPF framework, database tables are mapped to classes. The user of such a framework has to do with objects and lists of objects. Not datasets or database tables. A class is built upon one or more database tables, and the properties of that class are mapped, more or less, to database fields. This is a simplistic description though. Anyway, this mapping technique has some limitations, especially when the underlying database schema is not going to remain unaltered for ever (in my country, schema alterations happen very frequently). If the schema changes, then a re-mapping of the objects and a re-compilation of the application is required.

Besides ORMs, some programming environments provide a so called typed dataset, that is a dataset-like object where field names of an underlying database table are already properties of a datatable. PowerBuilder and .NET are examples of such environments.

The whole idea of ORMs and typed-datasets is that programmers know classes and must handle just classes and objects. Nothing else. That religion also believes that any exposition to database tables and SQL statements such as SELECT, INSERT etc., may harm programmers' attitude and lead them to some kind of a disappointment.

So ORM frameworks, like a magician, build the illusion that data is local to the application and not in the database. And as Anders Hejlsberg says, "You may want to have the illusion that the data is not in a database. You can have that illusion, but it comes at a cost."

Tripous is not an ORM framework. (The truth is that, once upon a time, when Tripous was younger, it had a different tongue, Delphi Pascal namely, and it was an ORM-wanna-be. But that's history now.) Instead, Tripous is a "traditional" framework. And although it has a business object, the Broker class, it provides full access to database tables and fields. So Tripous gives you direct access to DataSet, DataTable, and DataRow objects. It does its best not to bother you, but sooner or later, you'll have to deal with those DataSomething creatures.

No N-tier

Sometimes there is a confusion of what a tier or a layer is. Let's make another hopeless attempt to clarify the issue.

A well designed database application owes to provide three distinct layers. Data Access, Business Logic, and Presentation or User Interface. Think of them as departments in an organization. They must have clearly defined objectives, whithout overlaps. In practice, that's tough though. Those layers are like kids fighting for the same toy: data.

In short:

  • Data Access Layer hides the complexities of the various database types and provides uniformity in accessing database data by executing SELECT, INSERT, UPDATE etc., statements coming from the Business Logic Layer.
  • The Business Logic Layer solves the business problem, and performs business operations, taking into account the so-called business rules ("no discount more than 10 percent for any new customer for the first three months, unless he is the boss' cousin"), and provides data to the Presentation Layer.
  • The Presentation or User Interface Layer controls all those visual elements, such as windows, menus, and buttons the user uses in interacting with the application, presents data to the user, and returns any input to the Business Logic Layer.

A well designed application decouples business logic from presentation. Nevertheless, that layering is mostly a logical stratification. That is, in most cases, those three layers co-exist in the same physical executable.

Regarding physical layering, meaning different executable modules, there are two conflicting parties:

  • the traditional client-server party
  • and the modern multi-tier or n-tier party

In a client-server design, there are two physical layers: the application is the one. The database and the database server (MS SQL, Oracle etc.) is the other.

In a multi-tier design, sometimes called n-tier, there is another layer sitting between the application and the database: the Application Server, or AppServer for brevity. That application server serves client applications passing business objects (Customer, Supplier, Order, etc.) to them using a variety of technologies such as DCOM, CORBA, etc. In a multi-tier design, a client application talks to the application server and stays away from the database server.

Tripous is not a Swiss army knife. It is a modest system that knows its limits. It targets small to medium businesses with a small intranet or VPN. Under those conditions, a traditional client-server system is quite enough.

Table classification

Regarding business logic, a database application may logically be divided into business modules. Each module, say Customer or Store or Sales module, uses a set of correlated tables. For instance, an imaginary Sales module may use the CUSTOMER, MATERIAL, TRADE, and TRADE_LINES tables.

Tripous sees data tables in a certain way. A table may belong to one of the following categories in regard to the nature of data and the number of rows it may have: Master table, LookUp table, Transaction table, and Correlation table.

  • Master is a table when there are other tables that have foreign keys to it. A master table usually has more than two or three fields and many thousand rows. A CUSTOMER or MATERIAL table is considered to be a master table. A master table may contain foreign keys to other master or lookup tables.
  • Lookup is a table with a few fields and a few, at most hundred, rows. A lookup table is a literal provider to other tables that maintain a foreign key to it. It usually has ID, CODE, and NAME fields. An OCCUPATION or MEASURE_UNIT or COUNTRY table is considered to be a lookup table. A perfect lookup table contains no foreign keys to other tables.
  • Transaction table is a table that records transactions of master tables. Sometimes they are called historical tables or even trade tables. A transaction table can easily have millions of rows. An ORDERS or TRADE table is considered to be a transaction table. Transaction data very often require two or even more tables in a master-detail relationship, forming a table tree. For instance, ORDERS and ORDER_LINES; where ORDERS, the master, contains information regarding the customer, the date of transaction etc., while ORDER_LINES, the detail, records information regarding goods, quantities, and prices, and an ID to ORDERS.
  • Correlation table is a table that correlates two, or even more, Master tables. Usually, a correlation table records only IDs from those Master tables and nothing more. A Correlation table usually records many to many relationships. For instance, a CAR and a DRIVER Master table may require a CAR_DRIVER Correlation table. A driver may have many cars in his responsibility.

Regarding table classification and table tree composition, Tripous rely on you, the programmer. Tripous offers the Broker class that represents a business module. A Broker is the business object in Tripous. Internally, Broker uses a TableSet class that handles the table-tree.

The TableSet class handles the set of tables of a business module, discussed above. Each table is represented by a DataTable instance. The TableSet.TopTable property denotes the top DataTable. TopTable is the top DataTable in a tree of DataTable objects where a DataTable of a lower level is the master in a master-detail relationship to DataTables of the next greater level.

Normalization

Normalization is a term used in database programming to describe the techniques involved in designing tables in order to minimize duplication of information.

For instance, in a CUSTOMER table, it's not wise to have an OCCUPATION_NAME string field. Instead, you use an OCCUPATION_ID foreign key field pointing to the ID primary key field of the OCCUPATION table. The same stands true for an ORDER_LINES Transaction table and a MATERIAL Master table. The ORDER_LINES should have a MATERIAL_ID, not a MATERIAL_CODE or MATERIAL_NAME field.

In short, a bit of information is stored once and only once, and in a certain table. Any other table that wants access to that bit of information just maintains a reference to it. The process of arranging tables and fields the way just described is called normalization.

Normalization is not a panacea. It is used in so called "Production Databases", that is, databases used in the daily processing of transactions. Normalization is not used in a "Warehouse database" where the primary use is for "data mining" operations.

You may check the entries Database Normalization, Data Warehouse, and Data Mining at wikipedia. But regarding normalization, please, don't spend much time reading those texts, unless you are a student looking for reference material for the next semester. Conventional wisdom is quite enough. At least for the area Tripous tries to cover.

Primary keys

Here is a rule Tripous strictly follows: every table in a database should have a field named ID (OK, if you insist, feel free to give it any name you want). That field:

  • is an Integer field or a GUID string field,
  • it is the Primary Key, that is
  • it uniquely identifies a row, and most importantly,
  • it has no business meaning at all.

An application user almost never sees that field.

Those unique IDs are called OIDs, Object Identifiers. Some SQL Servers provide built-in mechanisms for the generation of those integer unique IDs. There are two forms: auto-increment fields and unique number generators. MS SQL Server and Informix fall in the first category while Interbase/Firebird and Oracle in the second.

Data entry forms

A database application mainly consists of data entry forms. But how should a data entry form look like? Well, there are a few hundred different opinions on that. There are just four things, and believe me no more, one can do with data: SELECT, INSERT, UPDATE, and DELETE. A very limited repertoire. (Let's admit it. Our job as database application programmers is not that hard, but better keep the secret among us.)

A Tripous data entry form has two parts:

  • a browse (or list) part, and
  • an edit (or item) part.

The data in the list part comes by issuing a "select * from TableName" statement (that's not accurate though since that SELECT would be as complex as it gets). The application user can configure an optional WHERE clause of that SELECT statement each time the statement is executed. The data of the list part is displayed in a read-only browser grid.

The data in the edit part, when editing, comes by executing a "select * from TableName where Id = x" statement. The Id value comes from the list part after a user selection. When inserting, the edit part displays just empty controls.

The above is a short description of a Tripous Master form. A Master form serves Master and Transaction tables.

The second kind of Tripous data entry form is the List form. The list form has no separate edit part. All data entry happens in the browser grid, and changes are committed all at once. A list form serves Lookup tables.

SELECTing data for a report or any other BI purpose is a totally different thing than SELECTing data for data entry purposes. So we can safely ignore it for now.

Stored Procedures

No Stored Procedures. Tripous is allergic to Stored Procedures. Stored Procedures tie an application to a certain database server. Tripous targets small to medium businesses. Some of them, the wealthier, may already have a commercial database server. Some of them, the wiser, may not, because they want to cut down the cost and use an Open Source server. So anything that ties an application to a certain database server is not a good choice.

There is a rumor though that Stored Procedures are executed in a fraction of time than normal SQL statements, and that's because the database server pre-compiles the Stored Procedure code. I'm not quite sure about that rumor, but I have to say that a decent database server should retain execution plans for all SQL statements in its internal cache, not just Stored Procedure execution plans. And if it doesn't, then I consider that as a major flaw. I admit though that my opinion lacks the proper validity.

The secret is to use parameterized SQL. This is what Tripous does. Something like "select * from CUSTOMER where ID = :ID". From a database server point of view, this statement looks much better than the usual "select * from CUSTOMER where ID = 1234".

Installing and setting up

This section provides information and instructions on how to install and setup Tripous.

Tripous sources

Tripous library source code comes as a compressed file that contains just a single Visual Studio solution.

The solution contains the following folders:

  • Compact: Compact Framework (PDA) related assemblies.
  • Desktop: Normal .NET Framework related assemblies.
  • Plugins: A few plug-in assemblies Tripous already uses.
  • Printing: A work in progress to create a reporting library.

The Compact folder contains:

  • the TripousPPC5 project which is the Tripous library assembly for working with PDAs.
  • the DevAppPPC5 project which is the template project for creating Compact Framework (PDA) applications with Tripous.
  • the TripousPPC5.Design project which contains some designers for the TripousPPC5 assembly. Read the accompanying ReadMe.txt for further clarification.
  • the TripousPPC5.DesignAttributesHack project which is actually the best hack I was able to find in overcoming a genasm.exe problem related to CF design-time functionality.

The Desktop folder contains:

  • the Tripous project which is the Tripous library for working with PCs.
  • the DevAppDesktop project which is the template project for creating desktop applications with Tripous.

The Plugins folder contains:

  • the em_FastReport project which is a plug-in assembly (em_ stands for external module) for dynamically loading the excellent FastReport library.
  • the em_ScriptNet project which is a plug-in assembly for the excellent ScriptDotNet scripting library.
  • the em_SyntaxBox project which is a plug-in assembly for the excellent Puzzle.SyntaxBox.NET3.5 syntax highlighting editor.

(Tripous tries to follow a pluggable architecture with many code elements (i.e., forms and other classes) and not only with external module plug-ins. This means that you may provide your own code, overriding Tripous default solution, if any at all.)

The Printing folder contains a project which is going to be the Tripous Reporting Library, time permitting.

Installation and compilation

Just decompress the sources to a directory. There is no need to install anything in the GAC. Locate the solution file (TripousLib.sln) and double-click it. VS opens and loads the solution. Just rebuild the solution and everything should be ready. There is nothing more to say here except of an old wish I learned when I was a Jedi knight: "may the source be with you".

If you are going to do some Compact Framework development using Tripous, then you better read carefully the two ReadMe.txt files found in the TripousPPC5.Design and the TripousPPC5.DesignAttributesHack projects.

The compilation process produces the two assemblies you need to reference in any Tripous project:

  • RootFolder\Compact\TripousPPC5\bin\Debug\TripousPPC5.dll, for Compact Framework applications.
  • RootFolder\Lib\Desktop\Tripous\bin\Debug\Tripous.dll, for normal desktop applications.

Installing Tripous controls

  • Close all MS VS instances.
  • Reopen MS VS and create a solution with a Windows Forms Application project.
  • Make Form1 visible.
  • Go to Toolbox and create a new tab (right click and then Add tab).
  • Name the new tab as "Tripous Desktop".
  • Right click that tab and then "Choose items".
  • In Choose ToolBox Items, click Browse and navigate to RootFolder\Lib\Desktop\Tripous\bin\Debug\Tripous.dll.
  • Select all the controls the Tripous.dll assembly contains and click OK.

First Tripous Application

This section provides instructions on how to code your first Tripous application.

Create a new solution with a Windows Forms Application project. Name the project as DevAppDesktop. Rename the project's default namespace (Solution Explorer | right click on the project | Properties | Application | Default namespace) to Project. Add a reference to the Tripous.dll assembly.

Build (Shift + F6) the project.

MainForm

Rename Form1 to MainForm. Set its IsMdiContainer property to true. Your MainForm must inherit from Tripous.Forms.SysMainForm. Also, you have to add some using directives to the MainForm.cs file. Here it is:

C#
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Windows.Forms;
using System.Threading;
using System.IO;
using Tripous;
using Tripous.Forms;
using Tripous.Data;

namespace Project
{
    public partial class MainForm : SysMainForm
    {
        public MainForm()
        {
            InitializeComponent();
        } 
    }
}

The SysMainForm class is a base class used in deriving main forms. We'll examine it in depth in a later tutorial.

ApplicationManager

Add a new code file to the project, and name it ApplicationManager.cs. Here is the code:

C#
using System;
using System.Collections.Generic;
using System.Windows.Forms;
using System.Drawing;
using System.Data;
using Tripous;
using Tripous.Forms;
using Tripous.Data;
using Tripous.BusinessModel;
using Tripous.Sockets;

namespace Project
{
    class ApplicationManager : ApplicationManagerDesktop
    {
        /* construction */
        public ApplicationManager()
        {
            Db.ErrorsVisible = true;
            Variables["User.Enabled"] = true;
            Variables["CompanyId"] = 1;
        }
    }
}

The ApplicationManagerDesktop class inherits from the ApplicationManagerBase class. There must be one and only one application manager in any Tripous application. An application manager acts like a traffic policeman who wants to be a leader in a hard rock band. Authority and anarchy in the same package.

The Db class is the central class in the Tripous.Data namespace. Just a collection of helper static methods actually. The Tripous.Variables class is a collection of named variables that can be persisted to XML. Here, the ApplicationManager.Variables property is an instance of the Variables class and it maintains some useful settings.

The MainForm again

Go back to MainForm. Add a System.Windows.Forms.MenuStrip to MainForm and name it mnuMain. Add a System.Windows.Forms.ToolStrip to MainForm and name it ToolBar.

Add a System.Windows.Forms.StatusStrip to MainForm and name it StatusBar. Add seven System.Windows.Forms.ToolStripStatusLabel items to the StatusBar and name them as follows:

  • lblLight
  • lblHint
  • lblAppName
  • lblUser
  • lblProfile
  • lblDate
  • lblTime

Add a Tripous.Forms.SideArea to MainForm and name it LeftSide. SideArea is a Tripous control with the secret ambition to resemble MS VS's ToolBox some day.

Here are the declarations of those objects in my MainForm.Designer.cs file:

C#
private System.Windows.Forms.MenuStrip mnuMain;
private System.Windows.Forms.ToolStrip ToolBar;
private System.Windows.Forms.StatusStrip StatusBar;
private System.Windows.Forms.ToolStripStatusLabel lblHint;
private System.Windows.Forms.ToolStripStatusLabel lblLight;
private System.Windows.Forms.ToolStripStatusLabel lblUser;
private System.Windows.Forms.ToolStripStatusLabel lblAppName;
private System.Windows.Forms.ToolStripStatusLabel lblProfile;
private System.Windows.Forms.ToolStripStatusLabel lblDate;
private System.Windows.Forms.ToolStripStatusLabel lblTime;
private Tripous.Forms.SideArea LeftSide;

Here is the full IntializeComponent() method from my MainForm.Designer.cs file:

C#
private void InitializeComponent()
{
    this.mnuMain = new System.Windows.Forms.MenuStrip();
    this.ToolBar = new System.Windows.Forms.ToolStrip();
    this.StatusBar = new System.Windows.Forms.StatusStrip();
    this.lblLight = new System.Windows.Forms.ToolStripStatusLabel();
    this.lblHint = new System.Windows.Forms.ToolStripStatusLabel();
    this.lblAppName = new System.Windows.Forms.ToolStripStatusLabel();
    this.lblUser = new System.Windows.Forms.ToolStripStatusLabel();
    this.lblProfile = new System.Windows.Forms.ToolStripStatusLabel();
    this.lblDate = new System.Windows.Forms.ToolStripStatusLabel();
    this.lblTime = new System.Windows.Forms.ToolStripStatusLabel();
    this.LeftSide = new Tripous.Forms.SideArea();
    this.StatusBar.SuspendLayout();
    this.SuspendLayout();
    // 
    // mnuMain
    // 
    this.mnuMain.Location = new System.Drawing.Point(0, 0);
    this.mnuMain.Name = "mnuMain";
    this.mnuMain.Size = new System.Drawing.Size(570, 24);
    this.mnuMain.TabIndex = 0;
    this.mnuMain.Text = "menuStrip1";
    // 
    // ToolBar
    // 
    this.ToolBar.Location = new System.Drawing.Point(0, 24);
    this.ToolBar.Name = "ToolBar";
    this.ToolBar.Size = new System.Drawing.Size(570, 25);
    this.ToolBar.TabIndex = 1;
    this.ToolBar.Text = "toolStrip1";
    // 
    // StatusBar
    // 
    this.StatusBar.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
    this.lblLight,
    this.lblHint,
    this.lblAppName,
    this.lblUser,
    this.lblProfile,
    this.lblDate,
    this.lblTime});
    this.StatusBar.Location = new System.Drawing.Point(0, 389);
    this.StatusBar.Name = "StatusBar";
    this.StatusBar.Size = new System.Drawing.Size(570, 22);
    this.StatusBar.TabIndex = 0;
    this.StatusBar.Text = "statusStrip1";
    // 
    // lblLight
    // 
    this.lblLight.AutoSize = false;
    this.lblLight.BackColor = System.Drawing.Color.Green;
    this.lblLight.BorderSides = ((System.Windows.Forms.ToolStripStatusLabelBorderSides)(
       (((System.Windows.Forms.ToolStripStatusLabelBorderSides.Left | 
          System.Windows.Forms.ToolStripStatusLabelBorderSides.Top)
                | System.Windows.Forms.ToolStripStatusLabelBorderSides.Right)
                | System.Windows.Forms.ToolStripStatusLabelBorderSides.Bottom)));
    this.lblLight.BorderStyle = System.Windows.Forms.Border3DStyle.Etched;
    this.lblLight.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text;
    this.lblLight.Name = "lblLight";
    this.lblLight.Size = new System.Drawing.Size(10, 17);
    // 
    // lblHint
    // 
    this.lblHint.AutoSize = false;
    this.lblHint.Name = "lblHint";
    this.lblHint.Size = new System.Drawing.Size(340, 17);
    // 
    // lblAppName
    // 
    this.lblAppName.AutoSize = false;
    this.lblAppName.Name = "lblAppName";
    this.lblAppName.Size = new System.Drawing.Size(90, 17);
    // 
    // lblUser
    // 
    this.lblUser.AutoSize = false;
    this.lblUser.Name = "lblUser";
    this.lblUser.Size = new System.Drawing.Size(90, 17);
    // 
    // lblProfile
    // 
    this.lblProfile.AutoSize = false;
    this.lblProfile.Name = "lblProfile";
    this.lblProfile.Size = new System.Drawing.Size(100, 17);
    // 
    // lblDate
    // 
    this.lblDate.AutoSize = false;
    this.lblDate.Name = "lblDate";
    this.lblDate.Size = new System.Drawing.Size(60, 17);
    // 
    // lblTime
    // 
    this.lblTime.AutoSize = false;
    this.lblTime.Name = "lblTime";
    this.lblTime.Size = new System.Drawing.Size(40, 17);
    // 
    // LeftSide
    // 
    // 
    // LeftSide.Body
    // 
    this.LeftSide.Body.Cursor = System.Windows.Forms.Cursors.Default;
    this.LeftSide.Body.Dock = System.Windows.Forms.DockStyle.Fill;
    this.LeftSide.Body.Location = new System.Drawing.Point(0, 0);
    this.LeftSide.Body.Name = "Body";
    this.LeftSide.Body.Size = new System.Drawing.Size(9, 502);
    this.LeftSide.Body.TabIndex = 3;
    this.LeftSide.Dock = System.Windows.Forms.DockStyle.Left;
    this.LeftSide.IsExpanded = false;
    this.LeftSide.Location = new System.Drawing.Point(0, 49);
    this.LeftSide.Name = "LeftSide";
    this.LeftSide.IsPinned = false;
    this.LeftSide.Size = new System.Drawing.Size(14, 502);
    this.LeftSide.TabIndex = 3;
    this.LeftSide.Title = "Tools";
    // 
    // MainForm
    // 
    this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
    this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
    this.ClientSize = new System.Drawing.Size(570, 411);
    this.Controls.Add(this.LeftSide);
    this.Controls.Add(this.StatusBar);
    this.Controls.Add(this.ToolBar);
    this.Controls.Add(this.mnuMain);
    this.IsMdiContainer = true;
    this.Location = new System.Drawing.Point(0, 0);
    this.MainMenuStrip = this.mnuMain;
    this.Name = "MainForm";
    this.Text = "MainForm";
    this.StatusBar.ResumeLayout(false);
    this.StatusBar.PerformLayout();
    this.ResumeLayout(false);
    this.PerformLayout();
}

The user interface of our MainForm is ready. So now we can go back to the MainForm.cs code file.

Add the Tripous.Forms.ICommandHost interface to the ancestor list of the <code>MainForm class and implement its properties.

C#
public partial class MainForm : SysMainForm, ICommandHost
{

    #region ICommandHost Members

    MenuStrip ICommandHost.MenuStrip
    {
        get { return this.MainMenuStrip; }
    }

    ToolStrip ICommandHost.ToolBar
    {
        get { return this.ToolBar; }
    }

    SideArea ICommandHost.SideBar
    {
        get { return this.LeftSide; }
    }

    #endregion
    
    public MainForm()
    {
        InitializeComponent();
    }
}

The ICommandHost interface represents an object that can display commands and other information. Usually, this object is the main form. The ApplicationManager uses the ICommandHost object as the command displayer of the commands we add to it.

Add the following two private members to the MainForm class:

C#
/* private methods */
private void Application_Idle(object sender, EventArgs e)
{
    if (!DesignMode)
    {
        lblDate.Text = DateTime.Today.ToString("yyyy-MM-dd");
        lblTime.Text = DateTime.Now.ToString("HH:mm");

        lblAppName.Text = Sys.ApplicationTitle;
        lblUser.Text = User.UserName;
        lblProfile.Text = !string.IsNullOrEmpty(Manager.Profiles.Active) ? 
                           Manager.Profiles.Active : Sys.None;
    }
}

/* private properties */
private ApplicationManager Manager { 
        get { return ApplicationManager.Instance as ApplicationManager; } }

And then adjust the constructor to be as follows:

C#
public MainForm()
{
    InitializeComponent();

    if (!DesignMode)
    {
        appManagerType = typeof(ApplicationManager);
        Application.Idle += new EventHandler(Application_Idle);  
    }
}

The appManagerType is a protected field, of type System.Type, in the SysMainForm class. The appManagerType provides the type used in creating the actual application manager object. That application manager instance is created by the SysMainForm.ApplicationInitialize() virtual method. We override that method and two others: the ApplicationFinalize() and the HandleEvent(). Here is the full MainForm.cs source code:

C#
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Windows.Forms;
using System.Threading;
using System.IO;
using Tripous;
using Tripous.Forms;
using Tripous.Data;

namespace Project
{
    public partial class MainForm : SysMainForm, ICommandHost
    {
        #region ICommandHost Members

        MenuStrip ICommandHost.MenuStrip
        {
            get { return this.MainMenuStrip; }
        }

        ToolStrip ICommandHost.ToolBar
        {
            get { return this.ToolBar; }
        }

        LeftSide ICommandHost.SideBar
        {
            get { return this.LeftSide; }
        }

        #endregion

        /* private methods */
        private void Application_Idle(object sender, EventArgs e)
        {
            if (!DesignMode)
            {
                lblDate.Text = DateTime.Today.ToString("yyyy-MM-dd");
                lblTime.Text = DateTime.Now.ToString("HH:mm");

                lblAppName.Text = Sys.ApplicationTitle;
                lblUser.Text = User.UserName;
                lblProfile.Text = !string.IsNullOrEmpty(Manager.Profiles.Active) ? 
                                   Manager.Profiles.Active : Sys.None;
            }
        }

        /* private properties */
        private ApplicationManager Manager { get { 
                return ApplicationManager.Instance as ApplicationManager; } }

        /* overrides */
        protected override void ApplicationInitialize()
        {
            base.ApplicationInitialize();

            Ini Ini = new Ini();
            ToolBar.Visible = Ini.ReadBool("ToolBar.Visible", false);
            LeftSide.Visible = Ini.ReadBool("SideBar.Visible", false);
            LeftSide.LastSize = Ini.ReadInteger("SideBar.LastSize", LeftSide.LastSize);
            if (LeftSide.Visible)
            {
                LeftSide.IsExpanded = Ini.ReadBool("SideBar.IsExpanded", false);
                if (LeftSide.IsExpanded)
                    LeftSide.IsPinned = Ini.ReadBool("SideBar.IsPinned", false);
                if (LeftSide.IsExpanded || LeftSide.IsPinned)
                    LeftSide.Width = Ini.ReadInteger("SideBar.Width", LeftSide.Width);
            }
        }
        protected override void ApplicationFinalize()
        {
            Ini Ini = new Ini();

            Ini.WriteBool("SideBar.Visible", LeftSide.Visible);
            Ini.WriteBool("SideBar.IsExpanded", LeftSide.IsExpanded);
            Ini.WriteBool("SideBar.IsPinned", LeftSide.IsPinned);
            Ini.WriteInteger("SideBar.Width", LeftSide.Width);
            Ini.WriteInteger("SideBar.LastSize", LeftSide.LastSize);
            Ini.WriteBool("ToolBar.Visible", ToolBar.Visible);


            base.ApplicationFinalize();
        }
        protected override void HandleEvent(ArgList Args)
        {
            if (!DesignMode && !IsDisposed)
            {
                base.HandleEvent(Args);

                switch (Args.ValueOf("EventName", string.Empty).ToString())
                {
                    case "Application.Executing":
                    case "Application.Waiting":
                        if (Args.ValueOf("Value", false))
                            lblLight.BackColor = Color.Red;
                        else
                            lblLight.BackColor = Color.Green;
                        break;
                }
            }
        }

        /* construction */
        public MainForm()
        {
            InitializeComponent();

            if (!DesignMode)
            {
                appManagerType = typeof(ApplicationManager);
                Application.Idle += new EventHandler(Application_Idle);
            }
        } 
    }
}

The Tripous.Ini class used in the <code>ApplicationInitialize() and ApplicationFinalize() methods simulates a classic Windows *.ini file using an XML file.

The overridden HandleEvent() method gets notifications sent by a Tripous.Broadcaster object. The Broadcaster class represents an object that sends event notifications to its subscribed listeners. The MainForm is such a listener since its base SysMainForm implements the Tripous.IListener interface. Any code can use a Broadcaster instance to send a notification to the subscribers of that Broadcaster.

Think of the Broadcaster as a mailing list. Someone sends a message to the list, and any list member gets a notification about that message. A receiver of such a message may take an action upon receiving the message or totally ignore it.

The Program class

Let's now concentrate on the Program.cs file. Here is the code:

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using System.Threading;
using Tripous;
using Tripous.Forms;

namespace Project
{

    static class Program
    {
        private const string AppUniqueId = "{Put a Guid string here}";
        static private InstanceManager im;


        [STAThread]
        static void Main(string[] args)
        {
            Sys.SetApplicationCulture(args);

            using (im = new InstanceManager(AppUniqueId))
            {
                if (!im.IsSingleInstance)
                {
                    Sys.ErrorBox("This application is already running!");
                    Application.Exit();
                    return;
                }

                Res.Add(Project.Properties.Resources.ResourceManager);

                Application.EnableVisualStyles();
                Application.SetCompatibleTextRenderingDefault(false);
                Application.Run(new MainForm());
            }
        }


        static Program()
        {
            Application.ThreadException += 
              new System.Threading.ThreadExceptionEventHandler(Application_ThreadException);
            Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
            AppDomain.CurrentDomain.UnhandledException += 
              new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
        }

        static void CurrentDomain_UnhandledException(object sender, 
                    UnhandledExceptionEventArgs e)
        {
            if ((e.ExceptionObject is Exception) && !e.IsTerminating)
            {
                try
                {
                    Logger.Log(e.ExceptionObject as Exception);
                }
                catch
                {
                }
            }
        }

        static void Application_ThreadException(object sender, 
                    ThreadExceptionEventArgs e)
        {
            try
            {
                Logger.Log(e.Exception);
            }
            catch
            {
            }
        }
    }
}

Let's start from the Main() and talk about the InstanceManager. Use the InstanceManager if no more than one instance of your application is allowed to be executed at the same time. InstanceManager ensures that only one instance of a certain application is running. To achieve that, it uses a unique string that uniquely identifies the application. This is the reason for the AppUniqueId private field. Assign a GUID to it just to be sure (menu Tools | Create GUID).

The Tripous.Res class is a static class. It provides access to the resources of any ResourceManager registered to it by calling its Add() method. Here, the local Project.Properties.Resources.ResourceManager is added to the Res class resource managers.

The static constructor of the Program class just links some events that allow us to centrally handle any unhandled exceptions. The code of both events here just uses the static Tripous.Logger class to log information regarding the thrown exception. The <code>Logger class is used to log any kind of information, not exceptions only. The Logger class also, in a way similar to Broadcaster, informs any subscribed listener for any log event. The Logger does not persist the log information on its own. This is a job for special log listeners. Tripous already provides a log listener that saves log information to a database table. There are other log listeners though that they just display the log information to the user.

Profiles

In Tripous, a profile file is a plain XML file initially found in the same folder where the application executable resides. It is always named as Profiles.XML. Copy this file to the ...\bin\Debug folder of your application and rename it to Profiles.XML. A Tripous application moves this file to the folder the Environment.SpecialFolder.CommonApplicationData designates. In Windows Vista, this is the C:\ProgramData\ folder.

Image 1

A profiles file is a list of profile items. One of those profile items is the Active profile. The Active profile is denoted by the Active attribute of the root Profiles tag. A Tripous application, in the start up, asks the user to select the Active profile, if the Active profile is not defined. Also, if the user starts the application and keeps the Ctrl key pressed, Tripous displays the same "Select active profile" dialog box for the user to choose or create a new profile.

Each one of those profile items contains a list of Datastore items. A datastore item contains the connection settings to a database. A profile may contain many datastore connections because an application may use more than one database. The datastore item named as MAIN is considered the main database of the application. The main database is where Tripous system tables reside. Tripous uses a few database tables for its own needs. The naming of those tables follows the scheme SYS_TABLENAME.

Here is the content of a profile named as Sqlite from the profile file:

XML
<item Provider="Sqlite" ConnectionString="Alias=Sqlite; 
        Initial Catalog=[Data]\DevApp.db3;" Name="MAIN" />

The Provider attribute specifies the provider. Tripous has been using "providers" long before .NET 2 as a way to provide database server neutral access. After that comes the ConnectionString attribute. It is almost identical to a usual connection string used in ADO.NET. The Alias sub-attribute duplicates the Provider attribute. The [Data] literal is a placeholder. At runtime, Tripous replaces the placeholder with an actual path. SQLite and Firebird expect a file name. Not all servers do that, of course. The Name attribute specifies the value of the Name property of a data store object, which is a Tripous class. It is not the name of a database.

Here is the content of a profile named as MsSql from the same file:

XML
<item Provider="MsSql" ConnectionString="Alias=MsSql; 
   Data Source=localhost; Integrated Security=SSPI; Initial Catalog=DevApp" Name="MAIN" />

Don't bother to create those databases. Tripous knows how to create empty database files for all of those three servers specified in the Profiles.XML file.

ApplicationManager initialization and finalization

Adjust your ApplicationManager class code to be as follows:

C#
class ApplicationManager : ApplicationManagerDesktop
{
    private CommandSetsToolForm commandSetsToolForm;


    /* other overrides */
    protected override void RealizeCommandSets()
    {
        base.RealizeCommandSets();
        if (commandSetsToolForm != null)
            commandSetsToolForm.RefreshToolForm(commandSets);
    }

    /* initialization-finalization */
    protected override void DoInitialize()
    {
        base.DoInitialize();

        if (SideBar != null)
        {
            commandSetsToolForm = new CommandSetsToolForm();
            SideBar.AddForm(commandSetsToolForm);
            commandSetsToolForm.RefreshToolForm(commandSets);
        }

    }
    protected override void DoFinalize()
    {
        base.DoFinalize();
    }  

    /* construction */
    public ApplicationManager()
    {
        Db.ErrorsVisible = true;
        Variables["User.Enabled"] = true;
        Variables["CompanyId"] = 1;
    }
}

As you may recall, the actual ApplicationManager instance is created by the initialization code of the SysMainForm, specifically by the SysMainForm.ApplicationInitialize() method. That method, after creating the manager, calls the application manager's ApplicationInitialize() method. ApplicationManager.DoInitialize() is called as a result of that call sequence.

DoInitialize() creates a tool form for the command sets. A command set is a set of commands. A CommandSetsToolForm instance is created, as a child form to the LeftSide control of the MainForm. The application manager instructs CommandSetsToolForm to display its commands. We'll talk about commands and the Command class in a minute.

DoFinalize() here does nothing. It's, for now, just for symmetry.

RealizeCommandSets() is called when initializing and when any of the command sets changes. In Tripous, an end user may create command sets using Command objects as members of those sets. The user gives a unique name to each of those command sets. The CommandSetsToolForm tool form is one way to display command sets to the user. Another way is the ToolBar of the MainForm.

Database schema registration and creation

Tripous gives you the ability to create versioned database schema on the fly. SchemaDatastores, SchemaDatastore, and SchemaExecutor are the relative classes all found in the Tripous.Data namespace.

You register a SchemaDatastore instance to the SchemaDatastores for any major schema alteration.

A SchemaDatastore has Name and Version properties. Initially, you create a SchemaDatastore under the Name MAIN and Version = 1 which, let's say, is the schema of version 1 of your application. After a year of successful sales of your application, a need arises to alter that initial schema. Then you create another SchemaDatastore under the same Name MAIN, but this time with Version 2. That's all you need to do.

Tripous does the same for its own system tables. Check the ApplicationManagerBase.RegisterSystemSchema() method and the SystemSchema class.

ApplicationManager.RegisterSchemas() is the place to register schemas for your data stores. Here is the code for that method for our sample application. Add the method below to the ApplicationManager class:

C#
/* registration */
protected override void RegisterSchemas()
{
    base.RegisterSchemas();

    SchemaDatastore schema = schemaDatastores.FindForce(Sys.MAIN, 1);
    SchemaTable Table;


    /* Company */
    string cCOMPANY =
                @"
create table Company  (
   Id                   @PRIMARY_KEY
  ,Name                 varchar(56) not null  
);
";

    Table = schema.AddTable("Company", cCOMPANY);

    /* Trader */
    string cTRADER =
                @"
create table Trader (
   Id                   @PRIMARY_KEY
  ,Code                 varchar(32) not null
  ,Name                 varchar(48) not null  
  ,IsCustomer           integer default 1 not null 
  ,IsSupplier           integer default 0 not null 
  ,@COMPANY_ID          integer default -1 not null 

  ,constraint FK_Trader_00 foreign key (@COMPANY_ID) references Company (Id)
);
";

    Table = schema.AddTable("Trader", cTRADER);


    /* TradeItem */
    string cTRADE_ITEM =
                @"
create table TradeItem (
   Id                   @PRIMARY_KEY
  ,Code                 varchar(32) not null
  ,Name                 varchar(48) not null  
  ,Price                float default 0 not null 
  ,@COMPANY_ID          integer default -1  not null 

  ,constraint FK_TradeItem_00 foreign key (@COMPANY_ID) references Company (Id)  
);
";
    Table = schema.AddTable("TradeItem", cTRADE_ITEM);

    /* Trade */
    string cTRADE =
                @"
create table Trade (
   Id                   @PRIMARY_KEY
  ,Code                 varchar(32) not null
  ,TraderId             integer not null
  ,TradeDate            @DATE not null  
  ,@COMPANY_ID          integer default -1 not null 

  ,constraint FK_Trade_00 foreign key (@COMPANY_ID) references Company (Id)  
  ,constraint FK_Trade_01 foreign key (TraderId) references Trader (Id)  
);
";

    Table = schema.AddTable("Trade", cTRADE);

    /* TradeLines */
    string cTRADE_LINES =
                @"
create table TradeLines (
   Id                   @PRIMARY_KEY
  ,TradeId              integer not null
  ,TradeItemId          integer not null
  ,Qty                  float default 1 not null 
  ,Price                float not null

  ,constraint FK_TradeLines_00 foreign key (TradeId) references Trade (Id)  
  ,constraint FK_TradeLines_01 foreign key (TradeItemId) references TradeItem (Id)  
);

";
    Table = schema.AddTable("TradeLines", cTRADE_LINES);
}

Trader, TradeItem, and Trade are our master tables, in respect to Tripous terminology. TradeLines is a detail table to the Trade table.

@PRIMARY_KEY, @DATE, etc., are just place holders. Tripous replaces those placeholders with the actual data types used by the database server specified in the Profiles.XML file. That's all you need to do to have your schema automatically created. Future schema alterations of the same database are registered under the same Name and a greater Version. That way, it is not possible for your service and support personnel to forget (oh, tell me about that) to apply the required schema alterations when you ship the next version of your application.

When Tripous creates a schema like the above, it writes an entry to the SYS_INI system table in order to know the current version of the schema. The next time the application runs, it compares the two versions to decide what to do. If the schema registered to SchemaDatastores by the RegisterSchemas() method is older than the one existing in SYS_INI, it does nothing. So the above code is going to be executed once and only once, provided that either there is not a SYS_INI table yet, or the entry in the SYS_INI has a lesser or equal version than the one on the line:

C#
SchemaDatastore schema = schemaDatastores.FindForce(Sys.MAIN, 1);

A business model Registry

The Tripous.BusinessModel.Registry class is the registry of the tripous business model. There is a whole bunch of descriptor classes that describe other classes and operations to the Tripous system. Most of those descriptors are registered to the <code>Registry class which is the central registry regarding business model classes.

Most of the RegisterXXXX() methods of the application manager concern the Registry class and its descriptors. The main descriptor classes are:

  • BrokerDescriptor
  • FormDescriptor
  • Command, not a descriptor per se
  • LocatorDescriptor
  • CodeDescriptor

All those descriptors have unique names (Name property) among their group.

Broker registration

The Tripous.BusinessModel.Broker class is Tripous business object. It represents a business logic domain or business logic module.

A broker is actually a set of correlated tables, represented by DataTable objects, and code. A Broker can be defined in a declarative way using a registered BrokerDescriptor. A BrokerDescriptor class is a Tripous descriptor class that describes the structure and the properties of a Broker class. Later the system can create the actual Broker class instance using the description provided by the BrokerDescriptor as found in the Registry.

Here is the header of the BrokerDescriptors.Add() method:

C#
public BrokerDescriptor Add(string DatastoreName, string Name, string MainTableName, 
             string Title, string BrokerClassName, string CodeProducerName);

The ApplicationManager.RegisterBrokers() method is where the broker registration happens. Here is the code of that method for our tiny application. Add the method below to the ApplicationManager class:

C#
protected override void RegisterBrokers()
{
    base.RegisterBrokers();

    BrokerDescriptor Broker;
    TableDescriptor Table;
    JoinTableDescriptor JoinTable;

    /* Company */
    Broker = Registry.Brokers.Add(Sys.MAIN, "Company", "Company", 
                                  "Company", "SqlBroker", string.Empty);

    /* Trader */
    Broker = Registry.Brokers.Add(Sys.MAIN, "Trader", "Trader", 
             "Trader", "SqlBroker", "SIMPLE XXXXXX");

    Table = Broker.Tables.Add("Trader", "Trader");
    Table.Fields.Add("Id", SimpleType.Integer, 0, "Id", FieldFlags.None);
    Table.Fields.Add("Code", SimpleType.String, 32, "Code", 
                     FieldFlags.Visible | FieldFlags.Searchable | 
                     FieldFlags.Required | FieldFlags.ReadOnlyUI);
    Table.Fields.Add("Name", SimpleType.String, 48, "Name", 
                     FieldFlags.Visible | FieldFlags.Searchable | FieldFlags.Required);
    Table.Fields.Add("IsCustomer", SimpleType.Integer, 0, "Is customer", 
                     FieldFlags.Visible | FieldFlags.Required | 
                     FieldFlags.Boolean).DefaultValue = "1";
    Table.Fields.Add("IsSupplier", SimpleType.Integer, 0, "Is supplier", 
                     FieldFlags.Visible | FieldFlags.Required | FieldFlags.Boolean);
    Table.Fields.Add(Sys.CompanyFieldName, SimpleType.Integer, 0, 
                     "Company", FieldFlags.Required);

    /* TradeItem */
    Broker = Registry.Brokers.Add(Sys.MAIN, "TradeItem", "TradeItem", 
                                  "TradeItem", "SqlBroker", 
                                  "SIMPLE XXX-XXX");

    Table = Broker.Tables.Add("TradeItem", "TradeItem");
    Table.Fields.Add("Id", SimpleType.Integer, 0, "Id", FieldFlags.None);
    Table.Fields.Add("Code", SimpleType.String, 32, "Code", 
                     FieldFlags.Visible | FieldFlags.Searchable | 
                     FieldFlags.Required | FieldFlags.ReadOnlyUI);
    Table.Fields.Add("Name", SimpleType.String, 48, "Name", 
                     FieldFlags.Visible | FieldFlags.Searchable | FieldFlags.Required);
    Table.Fields.Add("Price", SimpleType.Float, 0, "Price", 
                     FieldFlags.Visible | FieldFlags.Required).DefaultValue = "0";
    Table.Fields.Add(Sys.CompanyFieldName, SimpleType.Integer, 0, 
                     "Company", FieldFlags.Required);


    /* Trade */
    Broker = Registry.Brokers.Add(Sys.MAIN, "Trade", "Trade", 
                                  "Trade", "SqlBroker", 
                                  "SIMPLE XXX-XXX");
    Broker.BrowserSql.Text =
@"
select
   Trade.Id                 as Id           
  ,Trade.Code               as Code        
  ,Trade.TradeDate          as TradeDate
  ,Trader.Name              as Trader__Name
from
  Trade
    left join Trader on Trader.Id = Trade.TraderId
";

    Broker.BrowserSql.DisplayLabels.Add("Trader__Name=Trader");

    Table = Broker.Tables.Add("Trade", "Trade");
    Table.Fields.Add("Id", SimpleType.Integer, 0, "Id", FieldFlags.None);
    Table.Fields.Add("Code", SimpleType.String, 32, "Code", 
                     FieldFlags.Visible | FieldFlags.Searchable | 
                     FieldFlags.Required | FieldFlags.ReadOnlyUI);
    Table.Fields.Add("TraderId", SimpleType.Integer, 0, 
                     "TraderId", FieldFlags.Required);
    Table.Fields.Add("TradeDate", SimpleType.Date, 0, "Date", 
                     FieldFlags.Visible | FieldFlags.Searchable | 
                     FieldFlags.Required).DefaultValue = "AppDate";
    Table.Fields.Add(Sys.CompanyFieldName, SimpleType.Integer, 0, 
                     "Company", FieldFlags.Required);

    Broker.LinesTableName = "TradeLines";
    Table = Broker.Tables.Add(Broker.LinesTableName, Broker.LinesTableName);
    Table.MasterTableName = "Trade";
    Table.MasterKeyField = "Id";
    Table.DetailKeyField = "TradeId";
    Table.Fields.Add("Id", SimpleType.Integer, 0, 
                     "Id", FieldFlags.None);
    Table.Fields.Add("TradeId", SimpleType.Integer, 0, 
                     "TradeId", FieldFlags.Required);
    Table.Fields.Add("TradeItemId", SimpleType.Integer, 0, 
                     "TradeItemId", FieldFlags.Required);
    {
        JoinTable = Table.JoinTables.Add("TradeItem", "TradeItemId");
        JoinTable.Fields.Add("Id", SimpleType.Integer, 0, "Id", FieldFlags.None);
        JoinTable.Fields.Add("Code", SimpleType.String, 32, 
                             "Item Code", FieldFlags.Visible | FieldFlags.Searchable);
        JoinTable.Fields.Add("Name", SimpleType.String, 48, 
                             "Item", FieldFlags.Visible | FieldFlags.Searchable);
        //JoinTable.Fields.Add("Price", SimpleType.Float, 0, 
        //                     "Item Price", FieldFlags.None);
    }
    Table.Fields.Add("Qty", SimpleType.Float, 0, "Qty", 
                     FieldFlags.Visible | FieldFlags.Required).DefaultValue = "1";
    Table.Fields.Add("Price", SimpleType.Float, 0, "Price", 
                     FieldFlags.Visible | FieldFlags.Required).DefaultValue = "0";
}

Some brokers are described in a great detail while others are not. It depends on the application needs and the business logic complexity. In any case, when an actual Broker instance is constructed, its description is checked by Tripous using database table information, and many corrections and completions may happen.

The BrokerClassName parameter of the Add() method corresponds to the BrokerDescriptor.TypeClassName property, and it is very important. The value of that parameter/property is used in obtaining the actual Broker class type.

All the above BrokerDescriptor items define the SqlBroker class as the BrokerClassName of the broker. Tripous.BusinessModel.SqlBroker is the base class of brokers that deal with database data. That SqlBroker class knows how to handle most of the common data entry scenarios, with information just provided by its descriptor. When the standard SqlBroker class is not enough, the programmer derives a new broker class from it, to serve its specific needs.

In the Tripous business model descriptor system, everything has its own descriptor class. The TableDescriptor describes tables of a BrokerDescriptor where the FieldDescriptor describes fields of a table. There is a JoinTableDescriptor that describes tables joined to another table, even in a recursive way. You see Tripous uses the information you provide when registering brokers in order to build SELECT, INSERT, UPDATE, and DELETE SQL statements for the "edit part" of the broker.

A BrokerDescriptor provides the BrokerDescriptor.BrowserSql property where you may define the SELECT statement of the "browser part" of a broker.

Master-detail relationships are easily defined as you can see by observing the Trade-TradeLines tables above. That same example contains a JoinTableDescriptor too.

Important: When a descriptor registration requires a class name, such as the SqlBroker above, you should provide the full name including the namespace, unless it is a Tripous class.

Locators? What are locators?

Locator is a Tripous notion, and it means a piece of code that knows how to locate a database record under certain WHERE criteria. There is a locator control (LocatorBox), a locator column for the DataGridView (LocatorColumn), a locator component (Locator) which incorporates the actual locator logic, and of course, a LocatorDescriptor.

The ApplicationManager.RegisterLocators() method is where the locator registration happens. Here is the code of that method. Add the method below to the ApplicationManager class.

C#
protected override void RegisterLocators()
{
    base.RegisterLocators();

    LocatorDescriptor Locator;

    /* Customer */
    Locator = Registry.Locators.Add("Customer", "TraderId", 
                                    "Trader", "Id");
    Locator.Fields.Add(SimpleType.Integer, "TraderId", "Id", "Trader");
    Locator.Fields.Add(SimpleType.String, "Trader__Code", 
                       "Code", "Trader", "Customer Code");
    Locator.Fields.Add(SimpleType.String, "Trader__Name", 
                       "Name", "Trader", "Customer");
    Locator.SelectSql.Text = @"select Id, Code, Name from Trader where IsCustomer = 1";

    /* TradeItem */
    Locator = Registry.Locators.Add("TradeItem", "TradeItemId", 
                                    "TradeItem", "Id");
    Locator.Fields.Add(SimpleType.Integer, "TradeItemId", "Id", "TradeItem");
    Locator.Fields.Add(SimpleType.String, "TradeItem__Code", 
                       "Code", "TradeItem", "TradeItem Code");
    Locator.Fields.Add(SimpleType.String, "TradeItem__Name", 
                       "Name", "TradeItem", "TradeItem");
    Locator.Fields.Add(SimpleType.Float, "Price", "Price", 
                       "TradeItem", "Price").Searchable = false;
}

Locators is a really advanced matter, and we'll forget it for now.

Command registration

A Command is analogous to a push button or menu. A Command has no visual representation at all though. Instead, it can be attached to a push button or menu item or something. Basically, a command represents something that the application executes. Either as a result of a user action, or as a request from some client code.

A Command, as with all registerable objects in Tripous, has a unique name. Commands form a tree in order to simulate menus, tree views, etc., that may be used to display commands. Commands are also used in putting together a CommandSet described above.

A Processor is a special command, it is the root of a command tree. As long as a Command has no Parent, it considers itself a Processor. A Command has a Kind, which indicates the type of command. There can be:

  • Container,
  • Separator,
  • Mdi,
  • Modal, or
  • Procedure.

The ApplicationManager.RegisterCommands() method is where command registration takes place. Here is the code of that method. Add the method below to the ApplicationManager class.

C#
protected override void RegisterCommands()
{
    base.RegisterCommands();

    Command P = mainProcessor;
    Command Container;

    /* Admin */           
    Container = Command.CreateContainer("ADMIN", 
                Res.GetString("Administration", "Administration"));
    P.InsertAfter("FILE", Container);

    Container.AddMdi("Trader", 
                     Res.GetString("cmdTrader", "Traders"), 
                     DataMode.Browse);
    Container.AddMdi("TradeItem", 
                     Res.GetString("cmdTradeItem", "Trade Items"), 
                     DataMode.Browse);
    Container.AddMdi("Trade", 
                     Res.GetString("cmdTrade", "Trades"), 
                     DataMode.Browse);
}

The application manager already has its own Command Processor defined. It actually is the Registry.MainProcessor object. The ApplicationManager.mainProcessor protected field returns Registry.MainProcessor. This is the processor Tripous itself uses to register its own system commands. The FILE command is one of those system commands.

Our RegisterCommands() method inserts a Container Command, named ADMIN, after the system's FILE command. Then it registers three MDI form command items. A command of Form type must create a Windows Form object when it is executed.

Tripous will use the commands found in the main processor, the Registry.MainProcessor, in order to create, on the fly, the MainForm's main menu items and other command representations. You do not create main menu items in Tripous. Just command items.

Form registration

When a Form command named "Trader" is executed, it tries to find a FormDescriptor with the same name. A FormDescriptor is a really simple descriptor, and it just provides the name of the Form class and a few other settings.

The ApplicationManager.RegisterForms() method is where the form registration is done. Here is the code of that method. Add the method below to the ApplicationManager class.

C#
protected override void RegisterForms()
{
    base.RegisterForms();

    FormDescriptor FormDes;

    FormDes = Registry.Forms.Add("Trader", "Traders", 
              "Project.TraderForm", "Trader", FormFlags.SingleInstance);
    FormDes = Registry.Forms.Add("TradeItem", "Trade Items", 
              "Project.TradeItemForm", "TradeItem", FormFlags.SingleInstance);
    FormDes = Registry.Forms.Add("Trade", "Trades", 
              "Project.TradeForm", "Trade", FormFlags.SingleInstance);
}

Tripous registers its own system forms with the same way.

As you can see, there are three form descriptor items named after the corresponding command items. Each FormDescriptor item defines its own Form class name: TraderForm, TradeItemForm, and TradeForm. Those form objects does not yet exist. Our next task is to create them.

Important: When a descriptor registration requires a class name, such as the TraderForm above, you should provide the full name including the namespace, Project.TradeForm, unless it is a Tripous class.

Creating Forms

We are going to create the TradeForm. Add a new form using the Visual Studio wizard (Solution Explorer | right click on the project | Add | Windows form).

Name the source code document TradeForm.cs and the form class TradeForm.

Be sure that the new form belongs to the Project namespace. Check both the TradeForm.cs and the TradeForm.designer.cs files just to be sure.

Build (Shift + F6) the project.

Go to TradeForm.cs and add a using to the Tripous.Forms namespace.

Change the TradeForm base class from Form to DataEntryBrokerForm. Here is the full code:

C#
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Tripous.Forms;

namespace Project
{
    public partial class TradeForm : DataEntryBrokerForm
    {
        public TradeForm()
        {
            InitializeComponent();
        }
    }
}

Build (Shift + F6) the project. Now if you double click on TradeForm in the Solution Explorer, you will see the browse part of the form, that is something like the following:

TradeForm.jpg

Switch to the Data tab page.

Add a Panel and set its Dock property to Top.

From the Tripous tab of the Toolbox, add a DataGridViewEx and set its Dock property to Fill.

From the Tripous tab of the Toolbox, add two TextBoxEx text boxes and a LocatorBox to the panel. Add LabelEx labels in front of those three controls.

Tripous.Forms.TextBoxEx provides the extra properties: <code>DataField and DataSourceName. Those two properties are used in binding the control to database data. DataField is mandatory. So set one to Code and the other to TradeDate. These are fields of the Trade database table. Leave the DataSourceName empty. An empty DataSourceName property of a TextBoxEx text box instructs Tripous to bind the control to the Broker.tblItem DataTable that is to the top table of the Broker.

You may use the word Item as the DataSourceName which has the same effect as leaving it empty. And of course, you may explicitly set the DataSourceName to Trade, which is the name of the top table as it is defined in the BrokerDescriptor earlier.

Tripous.Forms.LabelEx has, by default, its <code>TextAlign property set to MiddleRight. And it also contains code and logic to retain its right-hand position when its text changes. Setting the TextAlign property of a LabelEx to XXXRight and leaving enough free space from its left side makes localization of the form easier.

Tripous.Forms.LocatorBox is a very special control. Some of its functionality is described earlier. Set its <code>DataField property to TraderId and its DescriptorName property to Customer. Setting the DescriptorName to something other than an empty string instructs Tripous to search the Registry for a LocatorDescriptor with that Name and provide its settings to the control. It is possible though to drop a LocatorDescriptor on the form and assign that local LocatorDescriptor name to the control. The logic of finding a locator descriptor can be found in the DataEntryForm.FindLocatorDescriptor() virtual method.

Let's setup the DataGridViewEx now.

Name it gridLines. Set its DataSourceName property to the word Lines. This instructs Tripous to bind the control to BrokerDescriptor.LinesTableName. This is useful when a master table has just a single detail table, as is our case here with the Trade and TradeLines tables.

Tripous.DataGridViewEx provides the Locators property, which is a collection. Click the button next to the Locators property, add an entry, and set its DescriptorName property to TradeItem and its DataField property to TradeItemId. You do not have to create columns for the grid manually. Tripous has enough information now to setup the grid. That information was given earlier in the BrokerDescriptor and the LocatorDescriptor.

The data entry part of the TradeForm should now look like the following:

TradeForm_DataEntry.jpg

Create the other two forms, the TraderForm and the TradeItemForm form, as described above for the TradeForm.

Running the application

The test application should be ready now. Hit F5 to run the application with the debugger.

If you hold down the Ctrl key, the "Select active profile" dialog appears.

SelectActiveProfile.jpg

The user may select the active profile or create a new one. If the database of the MAIN data store of the active profile does not exist, Tripous will try to create it.

Warning: If you select the profile named "Firebird" as your active profile, then you should copy FirebirdSql.Data.FirebirdClient.dll from the ThirdParty directory to your bin directory. And of course, you must have Firebird installed.

Next, the login form appears.

LoginForm.jpg

After that, you may login, without restarting the application, as a different user, but you can not select a different profile.

If the database of the MAIN data store of the selected profile does not exist, Tripous will try to create it.

There are three predefined and hard-coded user accounts in Tripous. The highest is the account with user name god and password knows. The other two are service-trustno1 and sys-admin.

Enter the password and hit OK. Tripous creates the database, if not exists, and then starts the application. It then displays the MainForm.

The MainForm creates the ApplicationManager, and then it calls its ApplicationManager.ApplicationInitialize(). Check the Tripous.Forms.SysMainForm.ApplicationInitialize() method to see the sequence. The application creates and displays the menu items in the MainForm. The application is ready now.

DevAppDesktop.jpg

The second menu item in the menu bar, the one labeled as Administration, is the one that contains the menu items created after our own commands. It contains menu items that trigger command objects that end up creating and displaying the forms we have defined.

A form command, that is a Command with its Kind set to Mdi or Modal, is finally handled by the ExecuteFormCommand() and ExecuteStandardFormCommand() virtual methods of the Tripous.Forms.ApplicationManagerDesktop class, which is the base of the ApplicationManager class of this application. Here is the ExecuteStandardFormCommand():

C#
protected virtual bool ExecuteStandardFormCommand(Command Command)
{
    if (IsFormCommand(Command))
    {
        LastFormCommand = Command;
        DataEntryForm.Show(Command);
        return true;
    }

    return false; 
}

As you can see, it just calls the static method DataEntryForm.Show(), passing the Command object. DataEntryForm.Show() is a quite complex method that, in short, collects information from the Registry regarding the FormDescriptor and the BrokerDescriptor, creates the form instance and the broker instance, connects the two, and displays the form.

That's it. We have just created our first fully functional Tripous application.

License

This article, along with any associated source code and files, is licensed under The Eclipse Public License 1.0


Written By
Software Developer AntyxSoft
Greece Greece
I’m a (former) musician, programmer, wanna-be system administrator and grandpa, living in Thessaloniki, Greece.

Comments and Discussions

 
QuestionWhy do I get TripousException Error? Pin
RAND 45586630-Mar-12 3:44
RAND 45586630-Mar-12 3:44 
AnswerRe: Why do I get TripousException Error? Pin
Theo Bebekis30-Mar-12 6:10
Theo Bebekis30-Mar-12 6:10 
GeneralTripod & Tripous Pin
pap19647-Jun-10 23:18
professionalpap19647-Jun-10 23:18 
Just a small correction: tripod and tripous are not just synonyms, they are one and the same word. Tripous is tripod-s, where -s is the suffix, and d is eliminated before s, therefore o is extended to ou. That's why in genitive we get tripod-os.
GeneralRe: Tripod & Tripous Pin
Theo Bebekis8-Jun-10 1:39
Theo Bebekis8-Jun-10 1:39 
GeneralFantastic work! Pin
Dim.Keeper7-Jun-10 5:34
Dim.Keeper7-Jun-10 5:34 
GeneralExcellent work! Pin
RAND 4558666-Jun-10 5:58
RAND 4558666-Jun-10 5:58 
GeneralMy vote of 1 Pin
Not Active5-Jun-10 14:26
mentorNot Active5-Jun-10 14:26 
GeneralRe: My vote of 1 Pin
Theo Bebekis5-Jun-10 20:30
Theo Bebekis5-Jun-10 20:30 
GeneralRe: My vote of 1 Pin
Not Active6-Jun-10 4:47
mentorNot Active6-Jun-10 4:47 

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.