Rapid Reads News

HOMEmiscentertainmentcorporateresearchwellnessathletics

Implementation of a table model in MQL5: Applying the MVC concept


Implementation of a table model in MQL5: Applying the MVC concept

In programming, application architecture plays a key role in ensuring reliability, scalability, and ease of support. One of the approaches that helps achieve such goals is to leverage architecture pattern called MVC (Model-View-Controller).

MVC concept allows you to divide an application into three interrelated components: model (data and logic management), view (data display), and controller (processing user actions). This separation simplifies code development, testing, and maintenance, making it more structured and flexible.

In this article, we consider how to apply MVC principles to implement a table model in the MQL5 language. Tables are an important tool for storing, processing, and displaying data, and properly organizing them can make working with information much easier. We will create classes for working with tables: table cells, rows, and table model. To store cells within rows and rows within the table model, we will use the linked list classes from the MQL5 Standard Library that allow efficient storage and use of data.

Imagine the application as a theater production. There is a scenario that describes what should happen (this is the model). There is the stage -- what the viewer sees (this is view). And finally, there is the director who manages the entire process and connects other elements (this is the controller). This is the way the architectural pattern MVC -- Model-View-Controller operates.

This concept helps to separate responsibilities within the application. The model is responsible for data and logic, the view is responsible for display and appearance, and the controller is responsible for processing user actions. Such separation makes the code clearer, more flexible, and more convenient for teamwork.

Let's say you are creating a table. The model knows which rows and cells it contains and knows how to change them. The view draws a table on the screen. And the controller reacts when the user clicks "Add row" and passes the task to the model, and then tells the view to update.

MVC is especially useful when the application becomes more complex: new features are added, the interface is changing, and several developers are working. With clear architecture, it is easier to make changes, test components individually, and reuse the code.

This approach also has some drawbacks. For very simple projects, MVC may be redundant -- one will have to separate even what could fit into a couple of functions. However, for scalable, serious applications, this structure quickly pays off.

In summary:

MVC is a powerful architectural template that helps organize code, make it more understandable, testable, and scalable. It is especially useful for complex applications where separation of data logic, user interface, and management is important. For small projects, its use is redundant.

The Model-View-Controller paradigm fits our task very well. The table will be created from independent objects.:

The figure below schematically shows the structure of a 4x4 table model:

We will write all classes in a single test script file so that everything is in one file, visible and quickly accessible. In the future, we will distribute the written classes into their separate include files.

CList linked list is very well suited for storing tabular data. Unlike the similar CArrayObj list, it implements access methods to neighboring list objects located to the left and right of the current one. This will make it easy to move cells in a row, or move rows in a table, add and delete them. At the same time, the list itself will take care of the correct indexing of moved, added or deleted objects in the list.

But there is one nuance here. If you refer to the methods for loading and saving a list to a file, you can see that when loading from a file, the list class must create a new object in the virtual CreateElement() method.

This method in this class simply returns NULL:

This means that in order to work with linked lists, and provided that we need file operations, we must inherit from the CList class and implement this method in our class.

If you look at the methods of saving Standard Library objects to a file, you can see the following algorithm for saving object properties:

The first and second points are inherent in all implemented save/load methods that Standard Library objects possess. Accordingly, following the same logic, we want to know the type of the object saved in the list, so that when reading from a file, we can create an object with this type in the virtual CreateElement() method of the list class inherited from CList.

Plus, all the objects that can be loaded into the list -- their classes must be declared or created before the list class is implemented. In this case, the list will "know" which objects are "in question" and which need to be created.

In terminal directory \MQL5\Scripts\, create a new folder TableModel\, and in it -- a new file of test script TableModelTest.mq5.

Connect the linked list file and declare future table model classes:

The forward declaration of future classes is necessary here so that the linked-list class that inherits from CList, knows about these types of classes, as well as knows about the types of objects that it will have to create. To do this, we will write enumeration of object types, auxiliary macros, and enumeration of ways to sort lists:

The function that returns description of object type is based on the assumption that all names of object type constants begin with "OBJECT_TYPE_" substring. Then you can take the substring following this one, convert all the characters of the resulting row to lowercase, convert the first character to uppercase, and clear all spaces and control characters from the final string on the left and right.

Let us write our own linked list class. We will continue writing the code further in the same file:

The CListObj class is our new linked list class, inherited from Standard Library CList class.

The only variable in the class will be the one in which the type of the object being created will be written. Since CreateElement() method is virtual and must have exactly the same signature as the parent class method, we cannot pass the type of the object being created to it. But we can write this type to a declared variable, and read the type of the object being created from it.

We must redefine two virtual methods of the parent class: method of uploading from a file and method of creating a new object. Let us consider them.

Method uploading the list from a file:

Here, the beginning of the list is first controlled, its type and size i.e. the number of elements in the list; and then, in a loop by the number of elements, beginning-of-data markers of each object and its type are read from the file. The resulting type is written to variable m_element_type and a method for creating a new element is called. In this method, a new element with the received type is created and written to node pointer variable, which, in turn, is added to the list. The entire logic of the method is explained in detail in the comments. Let us consider a method of creating a new list item.

Method creating a list item:

This means that before calling the method, the type of the object being created is already written in m_element_type variable. Depending on item type, a new object of appropriate type is created, and a pointer to it is returned. In the future, when developing new controls, their types will be written into ENUM_OBJECT_TYPE enumeration. And new cases will be added here to create new types of objects. The linked list class based on the standard CList is ready. Now it can store all objects of known types, save lists to a file and upload them from the file and restore them correctly.

A table cell is the simplest element of a table that stores a certain value. Cells compose lists representing table rows. Each list represents one table row. In our table, cells will be able to store only one value of several types at a time -- a real, integer, or string value.

In addition to a simple value, a cell can be assigned one object of a known type from ENUM_OBJECT_TYPE enumeration. In this case, the cell can store a value of any of the listed types, plus a pointer to an object, the type of which is written to a special variable. Thus, in the future, View component can be instructed to display such an object in a cell in order to interact with it using Controller component.

Since several different types of values can be stored in one cell, we will use union to write, store, and return them. Union is a special type of data that stores several fields in the same memory area. A union is similar to a structure, but here, unlike in a structure, different terms of the union belong to the same memory area. While in the structure, each field is allocated its own memory area.

Let's continue writing the code in the already created file. Let us start writing a new class. In the protected section, we write a union and declare variables:

In the public section, write access methods to protected variables, virtual methods, and class constructors for various types of data stored in a cell:

In the methods for setting values, it is done so that the type of the value stored in the cell is set first, and then the flag of the feature to edit values in the cell is checked. And only when the flag is set, the new value is saved in the cell:

Why is it done this way? When creating a new cell, it is created with the real type of the stored value. If you want to change the type of value, but at the same time the cell is not editable, you can call the method to set the value of the desired type with any value. The type of the stored value will be changed, but the value in the cell itself will not be affected.

The data cleaning method sets numeric values to zero, and enters a space in string values:

Later, we will do it differently -- so that no data is displayed in cleared cells -- to keep the cell empty, we will make an "empty" value for the cell, and then, when the cell is cleared, all the values recorded and displayed in it will be erased. After all, zero is also a full -- fledged value, and now when the cell is cleared digital data is filled with zeros. This is incorrect.

In parametric constructors of the class, cell coordinates in the table are passed -- number of row and column and the value of the required type (double, long, datetime, color, string). Some types of values require additional information:

In constructors with these types of values stored in cells additional parameters are passed to set the format of values displayed in cells:

Method for comparing two objects:

The method allows you to compare parameters of two objects by one of three comparison criteria -- by column number, by row number, and simultaneously by row and column numbers.

This method is necessary to be able to sort table rows by values of table columns. This will be handled by Controller in subsequent articles.

Method for saving cell properties to file:

After writing the starting data marker and the object type to the file, all the cell properties are saved in turn. The union must be saved as a structure using FileWriteStruct().

Method for loading cell properties from file:

After reading and checking beginning-of-data markers and type object, all the properties of the object are loaded in turn in the same order as they were saved. Unions are read using FileReadStruct().

Method that outputs object description to the log:

Here, the object description is simply printed to the log.

A table row is essentially a linked list of cells. The row class must support adding, deleting, and reordering cells in the list to a new location. The class must have a minimum-sufficient set of methods for managing cells in the list.

Let's continue writing the code in the same file. Only one variable is available in class parameters -- the row index in the table. All the rest are methods for working with row properties and with a list of its cells.

Since rows can only be compared by their single parameter - row index - this comparison is implemented here. This method will be required to sort out table rows.

Overloaded methods for creating cells with different types of stored data:

Five methods for creating a new cell (double, long, datetime, color, string) and adding it to the list end. Additional parameters of data output format into the cell are set with default values. They can be changed after the cell is created. First, a new cell object is created, and then added to the list end. If the object was not created, it is deleted to avoid memory leaks.

Method that adds a cell to the list end:

Any newly created cell is always added to the list end. Next, it can be moved to the appropriate position using methods of the table model class, which will be created later.

Overloaded methods for setting values to the specified cell:

Using the index, we get the required cell from the list and set the value for it. If the cell is non-editable, SetValue() method of cell object will for the cell set only the type of value being set. The value itself will not be set.

A method that assigns an object to a cell:

We get a cell object by its index and use its AssignObject() method to assign a pointer to the object to the cell.

Method that cancels an assigned object for a cell:

We get the cell object by its index and use its UnassignObject() method to remove the pointer to the object assigned to the cell.

Method that deletes a cell:

Using Delete() method of the CList class we delete the cell from the list. After a cell has been deleted from the list, indexes of remaining cells are changed. Using CellsPositionUpdate() method, we update indexes of all remaining cells in the list.

Method that moves a cell to the specified position:

In order for the CList class to operate on an object, this object in the list must be the current one. It becomes current, for example, when it is selected. Therefore, here we first get the cell object from the list by index. The cell becomes the current one, and then, using MoveToIndex() method of CList class, we are moving the object to the required position in the list. After successfully moving an object to a new position, indexes of the remaining objects must be adjusted, which is done using CellsPositionUpdate() method.

Method that sets row and column positions for all cells in the list:

The CList class allows you to find the current object index in the list. To do this, the object must be selected. Here we loop through all the cell objects in the list, select each one and find out its index using IndexOf() method of the CList class. Row index and the found cell index are set to the cell object using its SetPositionInTable() method.

Method that resets data of row cells:

In the loop, reset each next cell in the list using ClearData() cell object method. For string data, an empty row is written to the cell, and for numeric data, zero is written.

Method that returns description of the object:

A row is collected from object's properties and data and returned in the following format, for example:

Method that outputs object description to the log:

For non-tabular data display in the log, the header is first displayed in the log as row description. Then, if detailed display flag is set, descriptions of each cell are displayed in the log in a loop through the list of cells.

As a result, the detailed display of a table row to the log looks like this, for example (for a non-tabular view):

For tabular display, the result will be, for example, as follows:

Method that saves a table row to a file:

Save beginning-of-data markers, then object type. This is the standard header of each object in the file. After that, an entry to the object's properties file follows. Here, the row index is saved to the file, and then the list of cells.

Method uploading the row from a file:

Here everything is in the same order as when saving. First, the beginning-of-data marker and the object type are loaded and checked. Then the row index and the entire list of cells are loaded.

In its simplest form, the table model is a linked list of rows, which, in turn, contain linked lists of cells. Our model, which we create today, will receive a two-dimensional array of one of five types at the input (double, long, datetime, color, string), and build a virtual table from it. Further, we will extend this class to accept other arguments for creating tables from other input data. The same model will be considered the default model.

Let's continue writing the code in the same file \MQL5\Scripts\TableModel\TableModelTest.mq5.

The table model class is essentially a linked list of rows with methods for managing rows, columns, and cells. Write the class body with all the variables and methods, and then consider declared methods of the class:

Basically, there is only one object for a linked list of table rows and methods for managing rows, cells, and columns. And constructors that accept different types of two-dimensional arrays.

An array is passed to the class constructor, and a method to create a table model is called:

The method's logic is explained in the comments. The first dimension of the array is table rows, the second one is cells of each row. When creating cells, they use the same data type which array type is passed to the method.

Thus, we can create several table models, which cells initially store different types of data (double, long, datetime, color , and string). Later, after creating the table model, the types of data stored in the cells can be changed.

A method that creates a new empty row and adds it to the end of the list:

The method creates a new object of the CTableRow class and adds it to the end of rows list using AddNewRow() method. If an addition error occurs, the created new object is deleted and NULL is returned. On success, the method returns a pointer to the row newly added to the list.

Method that adds a row object to the list end:

Both of the methods discussed above are located in the protected section of the class, work in pairs, and are used internally when adding new rows to the table.

Method for creating a new row and adding it to the list end:

This is a public method. It is used to add a new row with cells to the table. The number of cells for the created row is taken from the very first row of the table.

Method for creating and adding a new row to a specified list position:

Sometimes you need to insert a new row not at the end of the list of rows, but between the existing ones. This method first creates a new row at the end of the list, fills it with cells, clears them, and then moves the row to the desired position.

Method that sets values to the specified cell:

First, get a pointer to the desired cell by the coordinates of its row and column, and then set a value to it. Whatever the value passed to the method to install it in the cell is, the method will select only the correct type for installation -- double, long, datetime, color, or string.

Method that sets accuracy of displaying data in the specified cell:

The method is relevant only for cells that store the real value type. It is used to specify the number of decimal places for the value displayed by the cell.

Relevant only for the cells displaying color values. It indicates the need to display color names if the color stored in the cell is present in the color table.

Method that assigns an object to a cell:

Method that cancels assignment of an object in a cell:

The two methods presented above allow you to assign an object to a cell, or remove its assignment. The object must be a descendant of the CObject class. In the context of articles about tables, an object can be, for example, one of the list of known objects from the ENUM_OBJECT_TYPE enumeration. At the moment, the list contains only cell objects, rows, and table models. Assigning them to a cell doesn't make sense. But enumeration will expand as we write articles about View component, where controls will be created. It is them that it would be expedient to assign to a cell, for example, the "drop-down list" control.

Method that deletes the specified cell:

The method gets the row object by its index and calls its method for deleting the specified cell. For a single cell in a single row, the method does not make sense yet, as it will reduce the number of cells in only one row of the table. This will cause cells to become out of sync with neighboring rows. So far, there is no processing of such deletion, where it is necessary to "expand" the cell next to the deleted one to the size of two cells so that the table structure is not disrupted. However, this method is used as part of the table column deletion method, where cells in all rows are deleted at once without violating the integrity of the entire table.

Method for moving a table cell:

Getting the row object by its index and calling its method for deleting the specified cell.

Method that returns the number of cells in the specified row:

Get the row by index and return the number of cells in it by calling CellsTotal() row method.

Method that returns the number of cells in the table:

The method goes through all the rows of the table and adds the number of cells of each row to the total result, which is returned. With a large number of rows in the table, such counting can be slow. After all the methods that affect the number of cells in the table are created, take them into account only when their number changes.

Method that returns the specified table cell:

Get the row by row index and return the pointer to the cell object by col index using GetCell() method of row object.

Method that returns description of the cell:

Get the row by index, get the cell from the row and return its description.

Method that displays cell description to the log:

Get a pointer to the cell by row and column indexes and, using Print() method of cell object, display its description in the log.

Method that deletes the specified row:

Using Delete() method of the CList class delete the row object by index from the list. After deleting the row, indexes of the remaining rows and cells in them do not correspond to reality, and they must be adjusted using CellsPositionUpdate() method.

Method that moves a row to the specified position:

In the CList class, many methods work with the current list object. Get a pointer to the required row, making it the current one, and move it to the required position using MoveToIndex() method of the CList class. After shifting the row to a new position, it is necessary to update indexes for the remaining rows, which we do using CellsPositionUpdate() method.

Method that sets row and column positions for all cells:

Go through the list of all rows in the table, select each subsequent row and set a correct index for it, found using IndexOf() method of the CList class. Then call CellsPositionUpdate() row method, which sets the correct index for each cell in the row.

Method that clears data of all cells in a row:

Each cell in the row is reset to an "empty" value. For now, for simplification purposes, the empty value for numeric cells is zero, but this will be changed later, since zero is also a value that needs to be displayed in the cell. And resetting a value implies displaying an empty cell field.

Method that clears data of all cells in the table:

Go through all the rows in the table, and for each row call RowResetData() method discussed above.

Method that returns description of the row:

Get a pointer to the row by index and return its description.

Method that displays row description in the log:

Get a pointer to the row and call Print() method of the received object.

Method that deletes table column:

In a loop through all rows of the table, get each next row and delete a required cell in it by column index. This deletes all cells in the table that have the same column indexes.

Method that moves a table column:

In a loop through all rows of the table, get each next row and move a required cell to a new position. This moves all cells in the table that have the same column indexes.

Method that clears data of column cells:

In a loop through all rows of the table, get each row and clear data in the required cell. This clears all cells in the table that have the same column indexes.

Method that returns description of the object:

A row is created and returned from some parameters of the table model in this format:

Method that outputs object description to the log:

First, the header is printed as description of the model, and then, if the detailed output flag is set, detailed descriptions of all rows of the table model are printed in the loop.

Method that outputs object description to the log in table form:

First, based on the number of cells in the very first row of the table, create and print the table header with names of table columns in the log. Then, go through all the rows of the table in a loop and print each one in tabular form.

After saving beginning-of-data marker and the type of the list, save the list of rows to the file using Save() method of the CList class.

Method for loading table model from file:

After loading and checking the beginning-of-data marker and the list type, load the list of rows from the file using Load() method of the CListObj class, discussed at the beginning of the article.

All classes for creating a table model are ready. Now, let's write a script to test model operation.

Continue writing the code in the same file. Write a script in which we will create a two-dimensional 4x4 array (4 rows of 4 cells each) with the type, for example, long. Next, open a file to write data of the table model into it and load data into the table from the file. Create a table model and check operation of some of its methods.

Each time the table is changed, we will log the received result using TableModelPrint() function, which selects how to print the table model. If value of macro PRINT_AS_TABLE is true, logging is done using PrintTable() method of CTableModel class, if value is false -- using Print() method of the same class.

In script, create a table model, print it out in tabular form, and save the model to a file. Then add rows, delete columns, and change the edit permission...

Then download the initial original version of the table from the file again and print out the result.

As a result, we get the following result of the script in the log:

To test working with the table model, adding, deleting and moving rows and columns, working with a file, complete the script:

In this case, the following is displayed in the log:

This output provides more debugging information. For example, here we see that setting the edit ban flag to a cell is displayed in program logs.

All the stated functionality works as expected, rows are added, moved, columns are deleted, we can change cell properties and work with files.

This is the first version of a simple table model that allows creating a table from a two-dimensional array of data. Next, we will create other, specialized table models, create View table component, and ahead we will have full-fledged work with tabular data as one of the controls of the user graphical interface.

The file of the script created today with the classes included in it is attached to the article. You can download it for self-study.

Previous articleNext article

POPULAR CATEGORY

misc

6166

entertainment

6944

corporate

5696

research

3608

wellness

5746

athletics

6991