What is Tripous?
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 SourceForge can be found at http://sourceforge.net/projects/tripous.
Tripous tutorials at CodeProject
Introduction
This is a tutorial on how to use Tripous data entry forms in order to display data from different types of data sources.
As the Tripous introduction tutorial describes, Tripous provides two types of data entry forms: the master and the list form. This is a tutorial on master forms.
A master data entry form has two parts: a browse (or list) part and an edit (or item) part. The browse part is just a read only DataGridView. The edit part may contain data bound controls that are used in editing a single data row or item.
The easiest way to use a data entry form is in conjunction with a Broker and basically a SqlBroker. The Broker is the base business class in Tripous. Most of the time, forms and brokers go together. But this is not the only scenario.
Here is the short hierarchy of Tripous form classes.
BaseForm: The base form class that provides logic for form initialization and "finalization".
DataEntryForm: The base class for data entry forms. Provides all the logic for browsing and searching in the list, as well as inserting, editing, and deleting a row.
DataEntryBrokerForm: The base class for data entry forms associated to a Broker.
In this tutorial, we are going to investigate four different cases:
The sample application
The sample application is not a full blown Tripous data entry application. The main form does not inherit from Tripous.Forms.SysMainForm. It is just a plain MDI container main form. Here is an image of that form that displays the four different form cases as menu items.

The main form is initialized in the OnShown() method.
protected override void OnShown(EventArgs e)
{
base.OnShown(e);
if (!DesignMode)
{
ObjectStore.Initialize();
InitializeForm();
}
}
ObjectStore is a Tripous static class that is a registry of objects. ObjectStore has the ability to search assemblies for classes or methods marked with the ObjectStoreItemAttribute attribute and register them automatically as object factories. Since this is not a full Tripous application, we must initialize ObjectStore manually.
The InitializeForm() belongs to the main form and starts the initialization sequence.
void InitializeForm()
{
Sys.Variables["Forms.MainForm"] = this;
ObjectStore.Add("Synchronizer.Primary", this as object);
Logger.Active = true;
InitializeDatabase();
InitializeDescriptors();
mnuCascade.Tag = MdiLayout.Cascade;
mnuTileHorizontal.Tag = MdiLayout.TileHorizontal;
mnuTileVertical.Tag = MdiLayout.TileVertical;
mnuArrangeIcons.Tag = MdiLayout.ArrangeIcons;
mnuExit.Click += new EventHandler(AnyClick);
mnuSqlMonitor.Click += new EventHandler(AnyClick);
mnuCascade.Click += new EventHandler(AnyClick);
mnuTileHorizontal.Click += new EventHandler(AnyClick);
mnuTileVertical.Click += new EventHandler(AnyClick);
mnuArrangeIcons.Click += new EventHandler(AnyClick);
mnuDataEntryBrokerFormWithSqlBroker.Click += new EventHandler(AnyClick);
mnuDataEntryFormWithASingleDataTable.Click += new EventHandler(AnyClick);
mnuDataEntryFormWithAGenericBindingList.Click += new EventHandler(AnyClick);
mnuDataEntryBrokerFormWithACustomBroker.Click += new EventHandler(AnyClick);
}
The InitializeForm() method informs Tripous that this form is the main form and that it is the primary synchronizer. Our main form implements the ISynchronizer interface which is used to synchronize thread calls.
Next, the logger is activated. The Logger class is used to log any kind of information, not exceptions only. Eventually, the logger 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. A full blown Tripous application provides a log listener that saves log information to a database table. There are other log listeners though that just display log information to the user. SqlMonitorForm is such a listener that displays any SQL statement while it is executed by Tripous. We are going to use SqlMonitorForm just to inspect some of those statements. So we need to activate the Logger class.
InitializeDatabase() and InitializeDescriptors() come next, and after that, there is code that just links events to event handlers. One of the four forms of this sample application uses SQL database data, so we need a database. We are going to use the excellent Sqlite as our database server. Tripous incorporates Sqlite already.
void InitializeDatabase()
{
string FileName = Path.GetFullPath(@"..\..\TripousDemo.db3");
DataProvider Provider = DataProviders.Find(ConnectionStringBuilder.Sqlite);
Provider.CreateDatabase("", FileName, "", "");
string ConStr = ConnectionStringBuilder.FormatConnectionString("",
ConnectionStringBuilder.Sqlite, FileName);
Datastore Datastore = Datastores.Add("MAIN", ConnectionStringBuilder.Sqlite, ConStr);
string CUSTOMER = @"
create table Customer (
Id @PRIMARY_KEY
,Code varchar(32)
,Name varchar(64)
)
";
Executor Executor = Datastore.CreateExecutor();
Executor.CreateTable(CUSTOMER);
}
The Tripous - Data access tutorial explains a lot about those data access classes such as DataProvider and Datastore. Here, this method gets the Sqlite Tripous DataProvider, creates the database if it does not exist, constructs a connection string, adds a Datastore to Datastores, creates an Executor, and uses that Executor in order to create a table in the database.
InitializeDescriptors() comes next.
void InitializeDescriptors()
{
BrokerDescriptor BrokerDes = Registry.Brokers.Add("Customer");
FormDescriptor FormDes = Registry.Forms.Add("Customer",
typeof(DataEntryBrokerFormWithSqlBroker).AssemblyQualifiedName);
Registry.MainProcessor.Add(CommandKind.Mdi, "Customer");
}
Let's start with the forms now.
This is the easiest case since we already have registered everything we need by the InitializeDescriptors().
When a Command object registered to Registry.MainProcessor and having its Kind property set to CommandKind.Mdi or CommandKind.Modal is executed, the Registry is searched for a FormDescriptor with a name as its own name. And then it creates the actual Form and the actual Broker, connects the two, and displays the form. All that happens automatically in a full blown Tripous application. In our tiny sample, we have to do it manually. So here is an excerpt from the AnyClick() method of the main form:
else if (sender == mnuDataEntryBrokerFormWithSqlBroker)
{
Command Cmd = Registry.MainProcessor["Customer"];
DataEntryForm.Show(Cmd);
}
The above creates an instance of the DataEntryBrokerFormWithSqlBroker which derives from the DataEntryBrokerForm.
When the associated menu item is clicked, we get the Customer Command from the Registry, and then we call the static Show() of the DataEntryForm class. That's all we have to do. An instance of the DataEntryBrokerFormWithSqlBroker class and an instance of the SqlBroker class are created and connected, and then the form displays itself. It is a data entry form that reads and writes its data to a relational database.
We can use the SQL Monitor menu item in order to display the SqlMonitorForm before displaying the data form, just to inspect the SQL executed by Tripous while browsing or inserting or searching.
Since this is not a full Tripous application, not all form's functionality is available. For example, reports are not supported. Reports, syntax highlighting, scripting, RAPI, etc., are supported using external plug-in modules that require an ApplicationManager class to load them. Nevertheless, almost all of the functionality is there. You can even execute a parameterized select. Here is the criteria part of the customer form:

In a normal Tripous application, the edit part of a master form hosts mostly Tripous controls that provide great flexibility in data binding. Those controls require full descriptor information. This sample uses standard .NET controls, and the binding is done by the code in the form.
protected override void BindControls()
{
base.BindControls();
if (bsBrowser != null)
{
textBox1.DataBindings.Add(Ui.Bind("Text", bsItem, "Code"));
textBox2.DataBindings.Add(Ui.Bind("Text", bsItem, "Name"));
}
}
In this form case, we use the DataEntryBrokerFormWithCustomBroker derived from DataEntryBrokerForm again. A DataEntryBrokerForm requires a Broker. And we are going to provide a custom Broker to that form. A Broker that has no idea about SQL data and databases.
Creating a custom Broker class
The Tripous.BusinessModel.Broker class is the base class for all brokers. It provides just empty virtual methods that may be used in inserting, editing, or deleting data. Here is the interface of the Broker class.
public class Broker : IBroker
{
static public readonly string BrowserPostfix = "__BROWSER";
protected Command processor = new Command();
protected object owner;
protected DataSet dataSet;
protected Variables variables;
protected Tables tables;
protected CodeProducer codeProducer;
protected DataMode state = DataMode.None;
protected object lastEditedId;
protected object lastCommitedId;
protected object lastDeletedId;
protected MemTable _tblBrowser; protected MemTable _tblItem; protected MemTable _tblLines; protected MemTable _tblSubLines; protected MemTable _tblBackup;
protected bool initialized;
protected bool initializing;
protected bool batchMode;
protected bool browserSelected;
protected bool selectBrowserOnce;
protected bool browserSetupDone;
protected bool isListBroker;
public virtual void Initialize(bool AsListBroker);
protected virtual void DoInitializeBefore();
protected virtual void DoInitialize();
protected virtual void DoInitializeAfter();
public void BrowserSelect();
public void BrowserCommit();
public void BrowserCancel();
protected virtual void DoBrowserSelect();
protected virtual void DoBrowserCommit();
protected virtual void DoBrowserSetup();
protected virtual void BrowserCheckCanCommit();
protected void BrowserSetup();
protected void BrowserAcceptChanges();
protected void BrowserRejectChanges();
protected virtual void DoBrowserAcceptChanges();
protected virtual void DoBrowserRejectChanges();
public void Insert();
public void Edit(object RowId);
public void Delete(object RowId);
public object Commit(bool Reselect);
public void Cancel();
protected virtual void DoInsertBefore();
protected virtual void DoInsert();
protected virtual void DoInsertAfter();
protected virtual void DoEditBefore(object RowId);
protected virtual void DoEdit(object RowId);
protected virtual void DoEditAfter(object RowId);
protected virtual void DoDeleteBefore(object RowId);
protected virtual void DoDelete(object RowId);
protected virtual void DoDeleteAfter(object RowId);
protected virtual void DoCommitBefore(bool Reselect);
protected virtual object DoCommit(bool Reselect);
protected virtual void DoCommitAfter(bool Reselect);
protected virtual void DoCancelBefore();
protected virtual void DoCancel();
protected virtual void DoCancelAfter();
protected virtual void CheckCanInsert();
protected virtual void CheckCanEdit(object RowId);
protected virtual void CheckCanDelete(object RowId);
protected virtual void CheckCanCommit(bool Reselect);
protected void AcceptChanges();
protected void RejectChanges();
protected virtual void DoAcceptChanges();
protected virtual void DoRejectChanges();
protected void Send(string EventName);
protected void Send(string EventName, string[] Names, object[] Values);
protected void Post(string EventName);
protected void Post(string EventName, string[] Names, object[] Values);
public Broker();
static public Broker Create(BrokerDescriptor BrokerDes,
bool Initialized, bool AsListBroker);
static public Broker Create(BrokerDescriptor BrokerDes, bool Initialized);
static public Broker Create(string Name, bool Initialized, bool AsListBroker);
static public Broker Create(string Name, bool Initialized) ;
public virtual void ReportsShowAdminDialog();
public virtual void ReportsShowMenuDialog();
public virtual bool ReportsExists(string Name);
public virtual bool ReportsExecute(string Name);
public virtual bool Copy();
public virtual bool Paste();
public virtual bool CanPaste();
public virtual string CurrentItemCodeName(int RowIndex);
public virtual DataRow Locate(string FieldName, object Value);
public DataRow Locate(object Value);
public virtual void BackUp();
public virtual TableDescriptor TableDescriptorOf(string TableName);
public virtual FieldDescriptor FieldDescriptorOf(string TableName,
string FieldName);
public virtual List<datarow> PickRows(string TargetTable,
string SourceSqlText, string[] VisibleColumns,
string TargetKeyName, string SourceKeyName);
public bool Initializing { get { return initializing; } }
public bool Initialized { get { return initialized; } }
public virtual int RowCount { get { return _tblBrowser == null ? 0 :
_tblBrowser.Rows.Count; } }
public virtual bool BrowserIsEmpty { get { return RowCount == 0; } }
public virtual bool IsBrowserModified { get { return _tblBrowser == null ?
false : _tblBrowser.GetChanges() != null; } }
public bool BatchMode { get; set; }
public DataSet DataSet { get ; }
public MemTable tblBrowser { get ; }
public MemTable tblItem { get ; }
public MemTable tblLines { get ; }
public MemTable tblSubLines { get ; }
public MemTable tblBackup { get ; }
public Tables Tables { get ; }
public CodeProducer CodeProducer { get ; }
public Variables Variables { get ; }
public Command Processor { get ; }
public virtual bool IsListBroker { get ; }
public virtual bool IsMasterBroker { get ; }
public object Owner { get; set; }
public DataMode State { get ; }
public virtual object LastEditedId { get ; }
public virtual object LastCommitedId { get ; }
public virtual object LastDeletedId { get ; }
public virtual string LinesTableName { get ; }
public virtual string SubLinesTableName { get ; }
public virtual string BackupTableName { get ; }
}
A broker, similar to a form class, divides its logic into two parts. A browse (or list) part and an edit (or item) part. Methods that start or contain the word "Browser" serve that browse part. Methods such as Commit(), Insert(), Delete(), Edit(), or DoCommit(), DoInsert(), DoDelete(), and DoEdit() serve the edit part of the broker. The "Do" prefix indicates a virtual method that is called by a method with the same name but without that "Do" prefix. Here is the Commit() method of the Broker class.
public object Commit(bool Reselect)
{
DataMode OldState = State;
try
{
DoCommitBefore(Reselect);
CheckCanCommit(Reselect);
lastCommitedId = DoCommit(Reselect);
DoCommitAfter(Reselect);
state = DataMode.Edit;
return lastCommitedId;
}
catch
{
state = OldState;
throw;
}
}
If you check the code of similar Broker methods, you'll see that they follow that same pattern.
try
{
DoXXXXBefore();
CheckCanXXXX();
DoXXXX();
DoXXXXAfter();
...
}
catch
{
...
throw;
}
The Tripous.BusinessModel.SqlBroker derives from the Broker class. A SqlBroker uses a BrokerDescriptor; it knows how to handle SQL data, and it provides suitable implementation for all those virtual methods of its Broker parent class. Eventually, the SqlBroker is able to handle many data scenarios without an extra line of code. But in this form case, we do not use a SqlBroker.
Instead, this sample provides a direct Broker derived class. The Broker class does not have a BrokerDescriptor. It relies on code. Take a look.
public class CustomBroker : Broker
{
static string fileName = Path.GetFullPath(@"..\..\Customers2.XML");
static DataSet DS = Db.CreateDataset();
static MemTable tblSource = new MemTable("Customers");
private void BrowserSelectInternal()
{
tblSource.CopyTo(_tblBrowser);
}
protected override void DoInitialize()
{
base.DoInitialize();
_tblBrowser = tables.Add("Customers" + BrowserPostfix);
tblSource.CopyStructureTo(_tblBrowser);
tblSource.CopyTo(_tblBrowser);
_tblItem = tables.Add("Customers");
tblSource.CopyStructureTo(_tblItem);
}
protected override void DoBrowserSelect()
{
BrowserSelectInternal();
}
protected override void DoInsert()
{
tblItem.Rows.Clear();
DataRow Row = tblItem.NewRow();
tblItem.Rows.Add(Row);
}
protected override void DoEdit(object RowId)
{
DataRow SourceRow = null;
if (!tblBrowser.Locate("Id", new object[] { RowId },
LocateOptions.None, out SourceRow))
Sys.Error("Can not edit a Customer. Row not found");
tblItem.Rows.Clear();
DataRow Row = tblItem.NewRow();
tblItem.Rows.Add(Row);
SourceRow.CopyTo(Row);
}
protected override void CheckCanCommit(bool Reselect)
{
base.CheckCanCommit(Reselect);
string S = tblItem.Rows[0].AsString("Code");
if (string.IsNullOrEmpty(S))
Sys.Error("No Code provided");
}
protected override object DoCommit(bool Reselect)
{
DataRow Row = tblItem.Rows[0];
string Id = Row.AsString("Id");
DataRow SourceRow = null;
if (!tblSource.Locate("Id", new object[] { Id },
LocateOptions.None, out SourceRow))
{
SourceRow = tblSource.NewRow();
tblSource.Rows.Add(SourceRow);
}
Row.CopyTo(SourceRow);
tblSource.WriteXml(fileName, XmlWriteMode.WriteSchema);
return Id;
}
protected override void DoDelete(object RowId)
{
DataRow SourceRow = null;
if (tblSource.Locate("Id", new object[] { RowId },
LocateOptions.None, out SourceRow))
{
tblSource.Rows.Remove(SourceRow);
tblSource.WriteXml(fileName, XmlWriteMode.WriteSchema);
}
}
static CustomBroker()
{
DS.Tables.Add(tblSource);
if (File.Exists(fileName))
{
tblSource.ReadXml(fileName);
}
else
{
DataColumn Column = tblSource.Columns.Add("Id", typeof(System.String));
tblSource.Columns.Add("Code", typeof(System.String));
tblSource.Columns.Add("Name", typeof(System.String));
}
}
public CustomBroker()
{
}
public override DataRow Locate(string FieldName, object Value)
{
DataRow Result = base.Locate(FieldName, Value);
if (Result == null)
{
BrowserSelectInternal();
Result = _tblBrowser.Locate(FieldName, new object[] { Value }, LocateOptions.None);
}
return Result;
}
}
Creating the DataEntryBrokerForm with the custom broker
Here is what happens when the associated menu item is clicked. There is no command object here.
else if (sender == mnuDataEntryBrokerFormWithACustomBroker)
{
DataEntryBrokerFormWithCustomBroker Form =
new DataEntryBrokerFormWithCustomBroker();
Form.MdiParent = this;
Form.Show();
}
There is not a big difference from the previous form case where we had a standard SqlBroker. Here is the code of the form.
public partial class DataEntryBrokerFormWithCustomBroker : DataEntryBrokerForm
{
protected override void BindControls()
{
base.BindControls();
if (bsItem != null)
{
textBox1.DataBindings.Add(Ui.Bind("Text", bsItem, "Code"));
textBox2.DataBindings.Add(Ui.Bind("Text", bsItem, "Name"));
}
}
public DataEntryBrokerFormWithCustomBroker()
{
InitializeComponent();
if (!DesignMode)
{
InitInfo = new ArgList();
InitInfo["Broker"] = new CustomBroker();
InitInfo["DefaultTitle"] = "DataEntryBrokerForm with a custom-made Broker";
}
}
}
The most critical information we provide to the InitInfo property is the "Broker" variable. We just create an instance of our CustomBroker class and pass that object. The InitializeBroker() our form inherits from DataEntryBrokerForm extracts that Broker instance from InitInfo and assigns a member field.
protected virtual void InitializeBroker()
{
if (initInfo != null)
{
broker = initInfo.ValueOf("Broker", broker);
if (broker != null)
{
broker.Initialize(IsListForm);
}
}
}
The associated menu displays the form. Again, no Command object.
else if (sender == mnuDataEntryFormWithASingleDataTable)
{
DataEntryFormWithASingleDataTable Form = new DataEntryFormWithASingleDataTable();
Form.MdiParent = this;
Form.Show();
}
Here is the code of the form:
public partial class DataEntryFormWithASingleDataTable : Tripous.Forms.DataEntryForm
{
string fileName = Path.GetFullPath(@"..\..\Customers.XML");
DataSet DS = Db.CreateDataset();
MemTable tblCustomers = new MemTable("Customers");
void TablesSetup()
{
DS.Tables.Add(tblCustomers);
if (File.Exists(fileName))
{
tblCustomers.ReadXml(fileName);
}
else
{
DataColumn Column = tblCustomers.Columns.Add("Id", typeof(System.Int32));
Column.AutoIncrement = true;
Column.AutoIncrementSeed = 1;
tblCustomers.Columns.Add("Code", typeof(System.String));
tblCustomers.Columns.Add("Name", typeof(System.String));
}
}
protected override void BindControls()
{
base.BindControls();
if (bsBrowser != null)
{
textBox1.DataBindings.Add(Ui.Bind("Text", bsBrowser, "Code"));
textBox2.DataBindings.Add(Ui.Bind("Text", bsBrowser, "Name"));
}
}
protected override void ProcessClose(StdCmd Cmd)
{
base.ProcessClose(Cmd);
if (Bf.Member(ClosingStdCmd, StdCmd.OK | StdCmd.Close))
tblCustomers.WriteXml(fileName, XmlWriteMode.WriteSchema);
}
public DataEntryFormWithASingleDataTable()
{
InitializeComponent();
if (!DesignMode)
{
TablesSetup();
InitInfo = new ArgList();
InitInfo.Add("tblBrowser", tblCustomers);
InitInfo.Add("tblItem", tblCustomers);
InitInfo["DefaultTitle"] = "DataEntryForm with a single DataTable";
}
}
}
There is just a single DataTable, the tblCustomers, which plays the role of both the browser and the item table.
Our form is not a Broker form. It inherits from Tripous.Forms.DataEntryForm, which has no idea about brokers. So, since there is not a Broker in this case, we pass the same tblCustomers as the tblBrowser and tblItem to the InitInfo property of the form. The schema of tblCustomers is created by the TablesSetup() method. When the form closes, the ProcessClose() method saves tblCustomers to a plain XML file.
Nothing special in showing the form.
else if (sender == mnuDataEntryFormWithAGenericBindingList)
{
DataEntryFormWithBindingList Form = new DataEntryFormWithBindingList();
Form.MdiParent = this;
Form.Show();
}
In this form case, we use a BindingListEx instance as our data store. BindingListEx inherits from the generic BindingList and provides a few extra capabilities. The type argument in that generic list is a humble Person class. Nothing special at all, but enough for this sample that tries to describe how to bind a Tripous DataEntryForm to a generic list.
public class Person
{
public Person()
{
}
public string Code { get; set; }
public string Name { get; set; }
}
Here is the code of the form.
public partial class DataEntryFormWithBindingList : DataEntryForm
{
string fileName = Path.GetFullPath(@"..\..\Persons.XML");
BindingListEx<Person> persons = new BindingListEx<Person>();
protected override void BindControls()
{
base.BindControls();
if (bsBrowser != null)
{
textBox1.DataBindings.Add(Ui.Bind("Text", bsBrowser, "Code"));
textBox2.DataBindings.Add(Ui.Bind("Text", bsBrowser, "Name"));
}
}
protected override void ProcessClose(StdCmd Cmd)
{
base.ProcessClose(Cmd);
if (Bf.Member(ClosingStdCmd, StdCmd.OK | StdCmd.Close))
XmlPersistor.SaveToFile(persons, fileName);
}
public DataEntryFormWithBindingList()
{
InitializeComponent();
if (!DesignMode)
{
ColumnInfoList BrowserColumnList = new ColumnInfoList();
BrowserColumnList.Add(new ColumnInfo("Code"));
BrowserColumnList.Add(new ColumnInfo("Name"));
persons.IsBound = true;
InitInfo = new ArgList();
InitInfo.Add("DataList", persons);
InitInfo["BrowserColumnList"] = BrowserColumnList;
InitInfo["DefaultTitle"] = "DataEntryForm and a generic BindingList";
XmlPersistor.LoadFromFile(persons, fileName);
}
}
}
Again, no Broker, no Command. Everything is coded. Our form inherits DataEntryForm, which has no idea about brokers.
In fact, since there is no DataTable, we have to provide information regarding the columns of the browse part of the form, the read-only DataGridView. We do that by creating a Tripous.Data.ColumnInfoList instance and some ColumnInfo objects. We pass that ColumnInfoList as the "BrowserColumnList" variable of the InitInfo property. The DoBrowserSetupGridColumns() method, our form inherits from its parent DataEntryForm class, has enough information now to do its job and create columns to the DataGridView.
Since there is no Broker or a DataTable to pass to the InitInfo, we just assign the "DataList" variable, passing the person's BindingListEx object. The DataEntryForm will use that list object as its data store.
The static Tripous.XmlPersistor class is used in serializing and de-serializing that list to an XML file. XmlPersistor and Tripous XML persisting capabilities deserve a separate tutorial.
Conclusion
Tripous DataEntryForm and DataEntryBrokerForm classes are quite flexible, and can be used as data entry forms with a variety of data sources: SqlBroker, a custom made broker, DataTables, or even generic lists.