Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / WinForms

Tripous - Forms and Brokers

4.00/5 (2 votes)
20 Jun 2010CPOL9 min read 18.4K   347  
A tutorial on how to use Tripous data entry forms in order to display data from different types of data sources.

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.

MainForm.jpg

The main form is initialized in the OnShown() method.

C#
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.

C#
void InitializeForm()
{
    Sys.Variables["Forms.MainForm"] = this;
    ObjectStore.Add("Synchronizer.Primary", this as object);

    Logger.Active = true;

    InitializeDatabase();
    InitializeDescriptors();

    /* assign MdiLayout values to menu item tags */
    mnuCascade.Tag = MdiLayout.Cascade;
    mnuTileHorizontal.Tag = MdiLayout.TileHorizontal;
    mnuTileVertical.Tag = MdiLayout.TileVertical;
    mnuArrangeIcons.Tag = MdiLayout.ArrangeIcons;

    /* events */
    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.

C#
void InitializeDatabase()
{
    /* construct the path to the database file */
    string FileName = Path.GetFullPath(@"..\..\TripousDemo.db3");

    /* get the proper DataProvider and create the database file */
    DataProvider Provider = DataProviders.Find(ConnectionStringBuilder.Sqlite);
    Provider.CreateDatabase("", FileName, "", "");

    /* construct the connection string to the database */
    string ConStr = ConnectionStringBuilder.FormatConnectionString("", 
                          ConnectionStringBuilder.Sqlite, FileName);

    /* create and add a Datastore object for the database */
    Datastore Datastore = Datastores.Add("MAIN", ConnectionStringBuilder.Sqlite, ConStr);


    /* this is the only table in the database */
    string CUSTOMER = @"
        create table Customer ( 
           Id      @PRIMARY_KEY 
          ,Code    varchar(32) 
          ,Name    varchar(64)   
        )
        ";

    /* create an Executor and use it to create the table in the database */
    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.

C#
void InitializeDescriptors()
{

    /* Create and add a BrokerDescriptor to the Registry.Brokers collection. 
       The minimum information required for a BrokerDescriptor 
       is the Name, the MainTableName and the TypeClassName.
       This particular Add() used here sets the Name, 
       MainTableName and Title properties of the broker descriptor
       to the passed in string, in this case Customer. 
       The TypeClassName property is set to SqlBroker.
       No other information is given to the descriptor, 
       so this is a very lazy initialization */
    BrokerDescriptor BrokerDes = Registry.Brokers.Add("Customer");


    /* Create and add a FormDescriptor to the Registry.Forms collection. 
       The minimum information required for a FormDescriptor 
       is the Name, the TypeClassName and the BrokerName.
       This particular Add() used here sets the Name and the BrokerName to Customer and
       the TypeClassName to an assembly qualified name of a demo form class.   */
    FormDescriptor FormDes = Registry.Forms.Add("Customer", 
           typeof(DataEntryBrokerFormWithSqlBroker).AssemblyQualifiedName);


    /* Create and add a Command to the Registry.MainProcessor. 
       The Command class provides the Kind property 
       which designates the function of the command.
       Here the Kind is set to CommandKind.Mdi, 
       which means that this commands should create an Mdi form.
       The Command.Name is required and here is set to Customer. 
       When a form command such as this command is executed, 
       it searches the Registry for a FormDescriptor
       with the same name as its own name. */
    Registry.MainProcessor.Add(CommandKind.Mdi, "Customer");
}

Let's start with the forms now.

A master form with a SqlBroker

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:

C#
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:

CustomerFormCriteria.jpg

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.

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

    /* If you use the standard TextBox and not the TextBoxEx provided by Tripous,
       then you have to manually bind that text box as following */
    if (bsBrowser != null)
    {
        textBox1.DataBindings.Add(Ui.Bind("Text", bsItem, "Code"));
        textBox2.DataBindings.Add(Ui.Bind("Text", bsItem, "Name")); 
    } 
}

A master form with a custom Broker

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.

C#
public class Broker : IBroker 
{
    static public readonly string BrowserPostfix = "__BROWSER";

    /* protected */
    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;             // ref
    protected MemTable _tblItem;                // ref
    protected MemTable _tblLines;               // ref
    protected MemTable _tblSubLines;            // ref    
    protected MemTable _tblBackup;              // ref  

    /* flags */
    protected bool initialized;
    protected bool initializing;
    protected bool batchMode;
    protected bool browserSelected;
    protected bool selectBrowserOnce;
    protected bool browserSetupDone;
    protected bool isListBroker; 

    /* initialization */
    public virtual void Initialize(bool AsListBroker);
    protected virtual void DoInitializeBefore();
    protected virtual void DoInitialize();
    protected virtual void DoInitializeAfter(); 

    /* browser */
    public void BrowserSelect();
    public void BrowserCommit();
    public void BrowserCancel();

    /* browser DoXXX methods */
    protected virtual void DoBrowserSelect();
    protected virtual void DoBrowserCommit();
    protected virtual void DoBrowserSetup();

    /* browser checks */
    protected virtual void BrowserCheckCanCommit();

    /* browser miscs */
    protected void BrowserSetup();
    protected void BrowserAcceptChanges();
    protected void BrowserRejectChanges();
    protected virtual void DoBrowserAcceptChanges();
    protected virtual void DoBrowserRejectChanges();

    /* item */
    public void Insert();
    public void Edit(object RowId);
    public void Delete(object RowId);
    public object Commit(bool Reselect);
    public void Cancel();

    /* item DoXXX before and after methods */
    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();

    /* item checks */
    protected virtual void CheckCanInsert();
    protected virtual void CheckCanEdit(object RowId);
    protected virtual void CheckCanDelete(object RowId);
    protected virtual void CheckCanCommit(bool Reselect);     

    /* item miscs */
    protected void AcceptChanges();
    protected void RejectChanges();
    protected virtual void DoAcceptChanges();
    protected virtual void DoRejectChanges();

    /* sending-posting data-related messages through Broadcaster */
    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);

    /* constructors */
    public Broker();


    /* static */
    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) ;

    /* local reports */
    public virtual void ReportsShowAdminDialog();
    public virtual void ReportsShowMenuDialog();
    public virtual bool ReportsExists(string Name);
    public virtual bool ReportsExecute(string Name);

    /* copy-paste */
    public virtual bool Copy();
    public virtual bool Paste();
    public virtual bool CanPaste();

    /* miscs */         
    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);

    /* properties */
    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.

C#
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.

C#
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.

C#
/// A custom Broker. It is not a SqlBroker. It just uses
/// a plain xml file to save its data.
/// There is the tblSource DataTable that acts as the data container.
/// That tblSource is not part of the Broker.Tables. It is just
/// a static field used as a helper.
public class CustomBroker : Broker
{
    /* these static fields play the role of the source data */
    static string fileName = Path.GetFullPath(@"..\..\Customers2.XML");
    static DataSet DS = Db.CreateDataset();
    static MemTable tblSource = new MemTable("Customers");

    /* private */
    private void BrowserSelectInternal()
    {
        tblSource.CopyTo(_tblBrowser);
    }

    /* overrides */
    /// Initializes the broker
    protected override void DoInitialize()
    {
        base.DoInitialize();

        /* By convention the browser table is named after the main (top) table 
           of the broker, plus the BrowserPostfix */
        _tblBrowser = tables.Add("Customers" + BrowserPostfix);


        /* add schema to the browser table and copy data to it */
        tblSource.CopyStructureTo(_tblBrowser);
        tblSource.CopyTo(_tblBrowser);


        /* create the top table and add schema to it */
        _tblItem = tables.Add("Customers");
        tblSource.CopyStructureTo(_tblItem);
    }
    /// Called by the Broker.BrowserSelect method
    /// and it actually selects the browser.
    protected override void DoBrowserSelect()
    {
        BrowserSelectInternal();
    }
    /// Called by theInsert to actually start an insert operation.
    protected override void DoInsert()
    {
        tblItem.Rows.Clear();
        DataRow Row = tblItem.NewRow();
        tblItem.Rows.Add(Row);
    }
    /// Called by the Edit to actually starts an edit operation.
    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);
    }
    /// Called by the Commit and throws an exception if, for some reason, commiting
    /// item is considered invalid.
    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");
    } 
    /// Called by the Commit() to actually commit changes made by the Insert or Edit
    /// methods, to the underlying table tree (database).
    /// Returns the row id of the tblItem commited row.
    protected override object DoCommit(bool Reselect)
    {
        DataRow Row = tblItem.Rows[0];
        string Id = Row.AsString("Id");


        /* commit to our "datastore" */
        DataRow SourceRow = null;
        if (!tblSource.Locate("Id", new object[] { Id }, 
                       LocateOptions.None, out SourceRow))
        {
            SourceRow = tblSource.NewRow();
            tblSource.Rows.Add(SourceRow);
        }

        Row.CopyTo(SourceRow);

        /* save to file */
        tblSource.WriteXml(fileName, XmlWriteMode.WriteSchema);

        return Id;
    }
    /// Called by the Delete to actually delete
    /// a row to the underlying table tree (database).
    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);
        }
    }

    /* construction */
    /// Static constructor
    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));
        }
    }
    /// Constructor
    public CustomBroker()
    {
    }

    /* public overrides */
    /// Locates a row in the tblBrowser and, if needed
    /// in the database by loading the row to the tblBrowser.
    /// The search is always performed to the tblBrowser or the main table of the broker.
    /// FieldName is the column name to search and Value the value to locate
    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.

C#
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.

C#
/// This is a form with a broker.
/// A DataEntryBrokerForm class used with a custom Broker descendant.
/// Since this form is not created by a Command we have to pass the proper
/// information to its InitInfo property.
public partial class DataEntryBrokerFormWithCustomBroker : DataEntryBrokerForm
{

    /* overrides */
    /// The virtual BindControls() is called by the form initialization sequence
    /// and it just binds controls to data.
    protected override void BindControls()
    {
        base.BindControls();

        /* If you use the standard TextBox and not the TextBoxEx provided by Tripous,
           then you have to manually bind that text box as following */
        if (bsItem != null)
        {
            textBox1.DataBindings.Add(Ui.Bind("Text", bsItem, "Code"));
            textBox2.DataBindings.Add(Ui.Bind("Text", bsItem, "Name"));
        }
    }

    /* construction */
    /// Constructor
    public DataEntryBrokerFormWithCustomBroker()
    {
        InitializeComponent();

        if (!DesignMode)
        {
            /* InitInfo is a property of the BaseForm class. It is used to convey 
               initialization information to the initialization sequence of the form.
               When a form is created by a Command class 
               with its Kind set to CommandKind.Mdi or Modal
               the Command prepares that InitInfo properly. 
               Here there is not a Command, so we just pass the minimum information. */
            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.

C#
protected virtual void InitializeBroker()
{
    if (initInfo != null)
    {
        broker = initInfo.ValueOf("Broker", broker);

        if (broker != null)
        {
           broker.Initialize(IsListForm);
        }
    } 
}

A master form bound to a single DataTable

The associated menu displays the form. Again, no Command object.

C#
else if (sender == mnuDataEntryFormWithASingleDataTable)
{
    DataEntryFormWithASingleDataTable Form = new DataEntryFormWithASingleDataTable();
    Form.MdiParent = this;
    Form.Show();
}

Here is the code of the form:

C#
/// This form uses no broker at all.
/// A DataEntryForm class with a single table
/// used both as the browser and as the top table.
/// Table data is saved to a plain xml file.
/// 
/// Since this form is not created by a Command we have to pass the proper
/// information to its InitInfo property.
public partial class DataEntryFormWithASingleDataTable : Tripous.Forms.DataEntryForm
{
    string fileName = Path.GetFullPath(@"..\..\Customers.XML");
    DataSet DS = Db.CreateDataset();
    MemTable tblCustomers = new MemTable("Customers");

    /* private */
    void TablesSetup()
    {
        DS.Tables.Add(tblCustomers);

        if (File.Exists(fileName))
        {
            tblCustomers.ReadXml(fileName);
        }
        else
        {
            /* add schema to the table */
            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));
        }

    }
        
    /* overrides */
    /// The virtual BindControls() is called by the form initialization sequence
    /// and it just binds controls to data.
    protected override void BindControls()
    {
        base.BindControls();

        /* Since there is no broker in this form and subsequentially no descriptors of any
           type, we have to manually bind our controls */
        if (bsBrowser != null)
        {
            textBox1.DataBindings.Add(Ui.Bind("Text", bsBrowser, "Code"));
            textBox2.DataBindings.Add(Ui.Bind("Text", bsBrowser, "Name"));
        }
    }
    /// Processes the Cmd. Cmd must be a "close" Cmd, that is OK, Cancel or Close.
    protected override void ProcessClose(StdCmd Cmd)
    {
        base.ProcessClose(Cmd);
        if (Bf.Member(ClosingStdCmd, StdCmd.OK | StdCmd.Close))
            tblCustomers.WriteXml(fileName, XmlWriteMode.WriteSchema);
    }
     
    /* construction */
    /// Constructor
    public DataEntryFormWithASingleDataTable()
    {
        InitializeComponent();

        if (!DesignMode)
        {
            TablesSetup();

            /* InitInfo is a property of the BaseForm class. It is used to convey 
               initialization information to the initialization sequence of the form.
               When a form is created by a Command class 
               with its Kind set to CommandKind.Mdi or Modal
               the Command prepares that InitInfo properly. 
               Here there is not a Command, so we just pass the minimum information. */
            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.

A master form bound to a generic BindingList

Nothing special in showing the form.

C#
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.

C#
public class Person
{
    public Person()
    {
    }

    public string Code { get; set; }
    public string Name { get; set; }
}

Here is the code of the form.

C#
/// This form uses no broker at all.
/// 
/// A DataEntryForm class with no DataTable at all.
/// It uses a BindingList, actually a BindingListEx which is a Tripous class.
/// BindindListEx is a bindable, sortable and searchable
/// generic BindingList with overridable methods.
public partial class DataEntryFormWithBindingList : DataEntryForm
{
    string fileName = Path.GetFullPath(@"..\..\Persons.XML");
    BindingListEx<Person> persons = new BindingListEx<Person>();

    /* overrides */
    /// The virtual BindControls() is called by the form initialization sequence
    /// and it just binds controls to data.
    protected override void BindControls()
    {
        base.BindControls();

        /* Since there is no broker in this form and subsequentially no descriptors of any
           type, we have to manually bind our controls */
        if (bsBrowser != null)
        {
            textBox1.DataBindings.Add(Ui.Bind("Text", bsBrowser, "Code"));
            textBox2.DataBindings.Add(Ui.Bind("Text", bsBrowser, "Name"));
        }
    }

    /// Processes the Cmd. Cmd must be a "close" Cmd, that is OK, Cancel or Close.
    protected override void ProcessClose(StdCmd Cmd)
    {
        base.ProcessClose(Cmd);

        /* Pass the persons BindingListEx to the XmlPersistor and ask it to save it */
        if (Bf.Member(ClosingStdCmd, StdCmd.OK | StdCmd.Close))
            XmlPersistor.SaveToFile(persons, fileName);

    }

    /* construction */
    /// Constructor
    public DataEntryFormWithBindingList()
    {
        InitializeComponent();

        if (!DesignMode)
        {
            /* prepare column information for the browser grid */
            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";

            /* load the persons BindingListEx using the XmlPersistor */
            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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)