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, DataTable
s, or even generic lists.