MQL5 for beginners: Anti-vandal protection of graphic objects

Dina Paches | 22 December, 2015

Vandalism is a form of destructive deviant human behavior,
when objects of art, culture, public and private
property are being destroyed or desecrated.

Wikipedia

Contents:


1. Introduction

One of the advantages of the MQL5 programming language is that with the existing MQL5 standard functions you can form codes for completing various tasks and achieving different goals when using the MetaTrader 5 trading terminal.

This article, written in a simple language and containing easy examples, considers two variants of implementing program's response actions to the control panel's graphic objects being deleted or changed. We are going to reveal how you can ensure that after the program is deleted, there are no ownerless objects present on the chart, over which the program may have lost control, because somebody or something has renamed them.

Example of a control panel before and after its objects' properties have been manually changed

Fig. 1. Example of a control panel's appearance before and after its object's properties have been manually changed

Options for constructing the response actions to external interference in the code, described in this article, may not be redundant for those cases where, for example, a third-party program launched on the chart and not directly intended for its cleanup, uses a function for deleting objects (ObjectsDeleteAll () or the one you create yourself), operating by the parameters set in it:

These options are also relevant, when it is advisable, including for the program's correct operation, to provide actions for accidental or intentional removal of its control panel's objects or manual change of their properties in the code.

This article can also be helpful for those who just started learning the event handling in the OnChartEvent() function.

Let me warn you straightaway that this article doesn't cover the creation of "militant" code reactions, whose objects were modified/removed without authorization. The main purpose of the terminal programs is to solve issues traders may have, therefore, robot war interference is not acceptable here.

For those who prefer militant actions, I wish to make a following analogy before you consider this move. Imagine that an office cleaner has swiped off a computer from somebody's desk with her mop or painted his/her desk or computer, believing that she created something beautiful. So if the owner of the damaged property in response to this will throw equipment, furniture and everyone present in the office along with the cleaner out of the window, his move will be considered as inappropriate. In addition to that, the person with aggressive behavior is unlikely to benefit from this situation.

Before proceeding with examples of two (out of many) possible ways to react to the vandalism towards objects, I believe it is also useful to mention one of the primary object protections provided in the MQL5/MQL4 programming languages.


2. One of the primary object protections in MQL5/MQL4

The access to created program's object properties would be more open without this protection.

Let me explain to you what I meant. For providing primary protection to objects from being deleted or modified through their properties - name, description, colors etc., an OBJPROP_HIDDEN feature is present and can be explicitly used. It sets or removes a ban for displaying a graphic object's name in the list of objects from the terminal's menu: Charts -> Objects -> List of objects. By default, the ban is set for objects displaying calendar events, trading history and also objects created with MQL5 programs.

The explicit ban (not set by default) can have the following scheme in the code:

ObjectSetInteger(chart_id,name,OBJPROP_HIDDEN,true);

where:

This scheme's practical implementation and coherence in the code can be found in the Documentation. By clicking on any link from the provided list of objects' types, you can view the examples of ready codes with functions for creating objects.

Objects that have a ban for displaying the graphic object's name in the list of objects set either explicitly or by default, can be viewed by pressing the All button for displaying all objects on this chart. It acts as a preliminary protection of graphic objects from the manual intervention in their settings through the List of objects.

List of objects on the chart before pressing "All" button

Fig. 2. List of objects on the chart before pressing the "All" button

List of objects on the chart after pressing "All" button

Fig. 3. List of objects on the chart after pressing the "All" button

The fact that the presence of graphic objects can't be completely hidden on the chart, even if you make them invisible, is rather an advantage than a disadvantage. You can quickly view, change or copy something in the objects' properties through the list, without checking the whole chart that may contain multiple objects and bars of previous years. Moreover, objects in the list can be sorted out by the type, name and some other parameters.

One of many other advantages of MetaTrader 5 and MetaTrader 4 trading terminals is the simplicity of writing automated assistants for these terminals yourself and using multiple assistants created by others. But do people that are never wrong exist? Plus, the preparation level for writing programs can be different. So can be the internal ethical borders. The programming language, just as people, may change and improve with time.

Therefore, the fact that you can ensure the correctness of forming graphic objects with a program at any moment, is a definite advantage. You can quickly find the required objects by the list and see their properties, rather than look for them throughout the whole chart. Furthermore, you can rest assured that the chart doesn't have any mistakenly or deliberately hidden objects, including the ones that are enormously generated by errors in the code.

It is also very likely that by displaying the list of all objects and selecting some of them to be deleted, there is a chance to accidentally destroy the control panel's objects that have to be on the chart at that point.

Before proceeding to variants of the program's response actions, we can take advantage of MQL5 and MQL4 programming languages mentioned earlier in the article. I refer to a capability to form codes that use different methods for solving various tasks.


3. Strategy for creating variants provided here

This may help you, if necessary, to create your own solutions in the code and save you from wasting time on looking for solutions using wrong or complicated routes.

To avoid sounding too formal and academic, I will tell you about the strategy described here the way it naturally occurred to me. Codes mentioned further are attached at the end of this article. For their operation you need to save the included file ObjectCreateAndSet, located in the Code Base, into your terminal's Include folder. It contains functions for creating objects and changing their properties with necessary checks. This file is not required for the attached test_types_of_chart_events indicator's operation.


3.1. Consideration and actions before deciding on practical implementation in the code

According to the Documentation, you can currently obtain and process information about nine types of events required for your own purposes and tasks, excluding changes on the chart and other users' events, by using a function OnChartEvent(). Some of them notify about removal, change and creation of graphic objects.

First of all, I was wondering how to implement the following in the code:

Both have to be implemented simultaneously with removing objects that have "alienated" from the chart due to someone or something renaming them, and without extra loops of event handling.

The first idea I came up with was about the total program's self-test revisions of its property, when the information about every event of removing or modifying objects is received, after all didn't seem like the best and most practical solution available. Then I thought it was because the total self-checks could imply a lot of unnecessary and not always justified actions.

For example, during the event handling of actions with objects, the chart object's name, to which any action is performed, is compared with names being tracked by the program. When tracking any type of event in the program, the information about it goes not only to the program's graphic objects, but all objects on the chart. The object's name has a type string, and string data is processed longer than other types. The chart may have multiple objects created manually or using other programs that can have a lot of various actions applied to them, thus, forming a large number of events.

Below is an approximate scheme for checking that names of objects match when the function of sending notifications about events of removing graphic objects is enabled. It is provided without preliminary handling for decreasing the amount of extra comparisons, which are applied when working with multiple graphic objects:

if(id==CHARTEVENT_OBJECT_DELETE)
        {
         string deletedChartObject=sparam;
         //---
         for(int i=QUANT_OBJ-1;i>=0;i--)
           {
            if(StringCompare(deletedChartObject,nameObj[i],true)==0)
              {
            /*there is a certain code with actions, if a subject's name that
            arrived during event has matched the
            program's object name completely*/
               return;
              }
           }
        }

Furthermore, the "revision" option (based on events or periodic) doesn't solve the problem of renaming objects by someone or something, and, eventually, the program may lose control over it leaving "ownerless" objects behind.

That is why I had a thought of using and detailing the flag system. Similar to the alarm where a semaphore flag equal to 0, for example, means that changing and deleting objects of the code must be treated by the program as an unauthorized external interference. The flag's value changes during personal actions of modifying and deleting objects provided by the code, so that deleting-changing events are no longer perceived as a "foreign invasion".

However, since chart events form a queue with their further handling in the OnChartEvent(), the system of triggering the flags when creating the "self-recovery" objects in the code for such cases wasn't particularly clear at that moment. Also, to avoid looping and simply extra repeated processing loops generated when handling deletion events of this type of action.

For example, there is a panel consisting of only few objects, and information output is enabled with Print() in the code of handling events of deleting this panel's objects. When notifying about these events a panel can easily fill up the log file with multiple notes within seconds during the objects' self-recovery, while frequently flashing on the chart from the continuous recreation. And this is despite the preliminary selection by the name, if the semaphore flag and self-recovery are applied using the following method (or equivalent):

else  if(id==CHARTEVENT_OBJECT_DELETE)
     {
     if(flagAntivandal==0)
        {
            string deletedChartObject=sparam;
            //---
            for(int i=QUANT_OBJ-1;i>=0;i--)
              {
               //--- actions when names fully match:
               if(StringCompare(deletedChartObject,nameObj[i],true)==0)
                 {
                  //--- print in the terminal's "Experts" tab:
                  TEST_PRINT(deletedChartObject);
                  //--- disable "alarm" for the time of executing program's "friendly" operations
                  flagAntivandal=-1;
                  //--- delete remaining objects:
                  ObjectsDeleteAll(0,prefixObj,0,-1);
                  //--- recreate panel
                  PanelCreate();
                 //--- redraw chart 
                  ChartRedraw();
                 //--- re-enable flag for "protection" of objects:
                  flagAntivandal=0;
                  return;
                 }
              }
         return;
        }
      return;
     }//else  if(id==CHARTEVENT_OBJECT_DELETE)

Removal and creation of objects will be looped with such code writing, forming a stream of event notifications. And the removal of other panel's objects for subsequent recovery may be needed, for example, if the canvas with other objects (buttons, input fields etc) is accidentally or intentionally deleted and has to be recreated with the one that doesn't overlap other objects.

If your computer doesn't have sufficient memory, then I don't recommend using the above mentioned code. If it does, then you can test it yourself by completely replacing it with handling events of deleting and changing in the attached test code test_count_click_2.

I haven't checked the maximum capacity for filling up a log file as a result of using a wrong flag, because I was forced to shut down the terminal 2-3 seconds after initiating events of a wrong "self-recovery" at most, while observing the continuous "blinking" of the program's objects and rapid, ongoing notes in the terminal's "Experts" tab. I deleted the considerably increased log file and emptied the Recycle Bin right away.

Furthermore, at that moment the solution for cases of renaming objects in the code remained unknown. An "anchor" that could help in this situation was missing.

That's why I decided to refresh my memory with information about types of chart events taken from the Documentation, and looked up the description of the ObjectSetString() function, where it says that after renaming an object two events are simultaneously formed:

After reading the Objects' properties section, I have decided that my anchor will be the objects' creation time, which can be determined using the OBJPROP_CREATETIME property.

For example, as you can see from the attached test_get_objprop_createtime script, the time of object creation equals the local time on a computer where the terminal runs:

The time of object creation equals the local time on a computer at the moment of creating an object.

Fig. 4. The time of object creation equals the local time on a computer at the moment of creating an object.

A called test script creates a button on the chart, determines the time of its creation and prints it via Print() in the log of the Experts tab. It sets and prints various types of time in the same tab of the terminal: computer's local time, trading server's estimated current time, last known server's time at the time of the last quote, GMT time. It then "freezes" for 10 seconds and deletes the button it created from the chart, thus terminating its operation.

The next step included preparing an action plan for a more clear formulation of solutions during the subsequent code creation. These final solutions must be versatile enough, so in the future there would be no need to write separate recovery actions for "each button". They must also consider a principle of peaceful coexistence on the chart.


The action plan consisted from the following:

3.1.1. To check what events and in what order they are displayed for a third-party program used as an independent observer, and for a program whose objects are affected externally:

At the same time it is necessary to display the times of creating objects and for convenience write down the obtained results as tables.

Accordingly, the following was required to implement the first point of the plan:

3.1.2. A programmatic indicator-assistant that acts as an external observer and describes the events occurring on the chart.

I chose an indicator, so it could operate on the chart with an Expert Advisor at the same time.

I am not going to discuss this assistant in depth, its full code is attached as the test_types_of_chart_events file. However, since it may be useful for you in the future when you work or familiarize yourself with events, I will mention here the following:

Fig. 5. External custom properties of a test indicator-observer

The indicator has notifications about events, that are not directly linked to the actions with objects, disabled by default. It has notifications enabled/disabled for five out of nine types of standard events that it operates with.

3.1.3. Other assistants participating in experiments:


3.2. Results of the conducted experiments

The results are displayed in the table below. The program whose objects are subject to actions "sees" standard notifications about events the same way as other programs on the chart do, which is the reason why only one table is provided. It is based on actions with "Button" and "Edit object" objects.

Action with object id lparam dparam sparam Name of event notifying about actions performed "manually" Time of creating object ** Name of event notifying about actions executed by "friendly" and third-party program
Time of creating object **
Creation of new object (not by renaming) 7
0 0 object's name CHARTEVENT_OBJECT_CREATE local time on computer where terminal runs CHARTEVENT_OBJECT_CREATE local time on computer where terminal runs
Renaming object (object with the previous name is deleted and a new object is created at the same time)
6

7

8
0

0

0
0

0

0
object's name,
object's name,
object's name
CHARTEVENT_OBJECT_DELETE

CHARTEVENT_OBJECT_CREATE

CHARTEVENT_OBJECT_CHANGE
creation time of the new object equals creation time of the object with the previous name no warnings
creation time of the new object equals creation time of the object with the previous name
Changing object's background 8 0 0 object's name СHARTEVENT_OBJECT_CHANGE no changes no warnings no changes
Changing object's coordinates on X-axis
8 0 0 object's name СHARTEVENT_OBJECT_CHANGE no changes no warnings no changes
Deleting object 6 0 0 object's name CHARTEVENT_OBJECT_DELETE *** CHARTEVENT_OBJECT_DELETE ***

 

Table 1. Fraction of what a friendly and a third party program can "see" from notifications about events of creation, change, removal of objects on the chart *


Notes:

* While an event notification is handled in the program, other notifications that appeared during or after handling, form a queue for handling in this program.

** I've noticed that if a chart contains graphic objects that are not recreated by the program after the terminal has restarted, then after a restart the OBJPROP_CREATETIME function returns the creation time of these objects' that equals 1970.01.01 00:00:00. Thus, previous time is "reset". This also happens with the chart's objects that are placed manually. When you switch time frames, the time of objects' creation is not reset.

*** If you try to determine the object's creation time using OBJPROP_CREATETIME during the notification about the object deleting event, then, since the object has already been deleted, the error message 4203 (incorrect identifier of a graphic object property) will be displayed.

If property changes were executed programmatically, it will not show such notifications. Like the rest of it, this is obviously done for a reason. Because a single terminal chart can operate with a lot of programs, including the ones that perform multiple actions with a number of graphic objects, and there can be a lot of open charts with these programs. So I assume that notifications for programs where event handling is applied to every programmatic change of properties of any chart's object would lead to a considerable increase in consumption of resources.

To avoid restricting users with a standard set of event notification types, the developers have given us opportunity to create personal notifications at any individual specific events in the code. The EventChartCustom() function is used for this purpose and generates notifications about the specified custom events for a personal or all charts of the terminal.

According to the experiment results some versatile solutions have been proposed.

They certainly can't be applied for any possible scenario. However, I believe they are capable of performing their tasks for control panel's objects, and also can be useful for developing your own options.

These solutions are based on:

The following is common for these solutions. Regardless of whether it is required to organize "protection" for one or all objects of a control panel, for implementing variants of objects' self-recovery and/or program's self-departure from the chart, you need to include in the code:

The variant of clearing the chart from the renamed objects, that used to be the program's objects, should have the following provided:

Furthermore, the order of implementing "security" actions in the code is important.

If you only construct a program's "self-departure" from the chart, triggered by unauthorized changes/deleting objects, in the code, then, depending on the task set, a single variable may be sufficient for a flag. With values, for example:
if(flagAntivandal==-1){return;}
IndicatorSetString(INDICATOR_SHORTNAME,short_name);
  • ExpertRemovе() for an Expert Advisor. If you are not familiar with this function yet, then please check Documentation for a description and example provided there for your information.

At least two flags will be required, if you are planing to create object "self-recovery" after the unauthorized interference in the code, where objects will be deleted and new ones created in their stead. One of them is equal to the previous flag in its functions. The second one is required to prevent the code looping from a possible reception of deletion events during objects' recovery actions. Actions will be slightly more complicated here. The second flag is adaptive and serves for an automatic self-adjustment to achieve the "alarm's" smooth operation and handling the events of removing objects. It "discharges" the unnecessary event handling. I will look into it further in the example 4.2.

Now having said all this, let's move on to examples.


4. Schemes of implementing variants during unauthorized modification or removal of program's objects

The example of a simultaneous implementation of a "self-recovery" in regards to some objects and a "self-departure" from the chart when changing/deleting other objects within the code is provided in the chapter 5 of this article. It describes the operational program's code scheme that can also become your assistant, if necessary.

We will progress from simple to complex matters by using the example of another test code that creates a panel on the chart for calculating mouse clicks on its objects, belonging to areas "Yes" and "No".

This is how a panel of the attached test code looks depending on trading terminal's language

Fig. 6. Panel of attached test code depending on trading terminal's language

This is a simple test code that facilitates tracking the operation schemes (described below) of two following action types:

The initial test indicator's code with the schemes described below not included is attached to the article under the name of test_count_click_0. During the implementation of two following schemes two varieties of this test code were consequently created with a consideration of the additions made. That is why you can see each scheme's operation straight away individually as a full code, and also test their operation on the chart by removing or deleting these codes' objects.

The additional lines for Print() output are included in the attached codes where these schemes are implemented, so that you could view the order of handling implemented schemes when changing or removing objects. Below are the code lines without auxiliary Print() output commands.


4.1. Program's "self-departure" from the chart with removal of modified objects

The full version of a working test code is attached under the name of test_count_click_1.

4.1.1. File creation with this scheme in the code:

The file test_count_click_0 is saved under the name of test_count_click_1. This can be done by opening the attached file containing number 0 in the title in the MetaEditor, and then choosing File -> Save as... from the top menu of the MetaEditor.

4.1.2. Variables declared beyond all functions in the scope of visibility to all program's parts, i.e. until the block of the OnInit() function:

The indicator's name in #define is replaced for a subsequent internal substitution:

#define NAME_INDICATOR             "count_click_1: "

A variable is added for the semaphore flag:

int flagAntivandal;
Followed by an array with size equal to the amount of "protected" objects for storing the times of creating objects:
datetime timeCreateObj[QUANT_OBJ];

A text array for storing two warning messages in case of the indicator's "self-departure" from the chart is added. One for a successful removal from the chart, second - if an error occurs during this operation. The array is declared in the scope of visibility to all parts of the program, since the output of these messages is provided in two languages (English and Russian), depending on the terminal's language.

string textDelInd[2];

Furthermore, messages stored in this array may be useful, providing the indicator's departure from the chart due to reception of the code's REASON_INITFAILED deinitialisation reason in the OnInit().

4.1.3. In the LanguageTerminal() function responsible for displaying texts' messages depending on the trading terminal's language:

Texts of messages are added at program's both successful and unsuccessful "self-departure" from the chart.

void LanguageTerminal()
  {
   string language_t=NULL;
   language_t=TerminalInfoString(TERMINAL_LANGUAGE);
//---
   if(language_t=="Russian")
     {
      textObj[3]="Да: ";
      textObj[4]="Нет: ";
      textObj[5]="Всего: ";
      //---
      StringConcatenate(textDelInd[0],"Не удалось удалиться индикатору: \"",
                        prefixObj,"\". Код ошибки: ");
      StringConcatenate(textDelInd[1],"Удалился с графика индикатор: \"",
                        prefixObj,"\".");
     }
   else
     {
      textObj[3]="Yes: ";
      textObj[4]="No: ";
      textObj[5]="All: ";
      //---
      StringConcatenate(textDelInd[0],"Failed to delete indicator: \"",
                        prefixObj,"\". Error code: ");
      StringConcatenate(textDelInd[1],
                        "Withdrew from chart indicator: \"",
                        prefixObj,"\".");
     }
//---
   return;
  }

4.1.4. In the PanelCreate() function that creates control panel's objects:

After creating objects the time of their creation is determined and stored in the array. In a simple version this may look approximately like this:

void PanelCreate(const long chart_ID=0,const int sub_window=0)
  {
   for(int i=NUMBER_ALL;i>=0;i--)
     {
      //--- fields for showing the number of clicks:
      if(ObjectFind(chart_ID,nameObj[i])<0)
        {
         EditCreate(chart_ID,nameObj[i],sub_window,X_DISTANCE+WIDTH,
                    Y_DISTANCE+(HEIGHT)*(i),WIDTH,HEIGHT,
                    textObj[i],"Arial",FONT_SIZE,"\n",ALIGN_RIGHT,true,
                    CORNER_PANEL,clrText[i],CLR_PANEL,CLR_BORDER);
         //--- determine and store the time of creating object:
         CreateTimeGet(chart_ID,nameObj[i],timeCreateObj[i]);
        }
     }
//---
   int correct=NUMBER_ALL+1;
//---
   for(int i=QUANT_OBJ-1;i>=correct;i--)
     {
      //--- fields with accompanying notes:
      if(ObjectFind(chart_ID,nameObj[i])<0)
        {
         EditCreate(chart_ID,nameObj[i],sub_window,X_DISTANCE+(WIDTH*2),
                    Y_DISTANCE+(HEIGHT)*(i-correct),WIDTH,HEIGHT,
                    textObj[i],"Arial",FONT_SIZE,"\n",ALIGN_LEFT,true,
                    CORNER_PANEL,clrText[i-correct],CLR_PANEL,CLR_BORDER);
         //--- determine and store the time of creating object:
         CreateTimeGet(chart_ID,nameObj[i],timeCreateObj[i]);
        }
     }
   return;
  }

4.1.5. The function's variant to determine and store the time of object's creation into a variable:

bool CreateTimeGet(const long chart_ID,
                   const string &name,
                   datetime &value
                   )
  {
   datetime res=0;
   if(!ObjectGetInteger(chart_ID,name,OBJPROP_CREATETIME,0,res))
     {
      Print(LINE_NUMBER,__FUNCTION__,", Error = ",
            GetLastError(),", name: ",name);
      return(false);
     }
   value=res;
   return(true);
  }

In case the program is on the chart during the terminal's emergency restart, for example, caused by freezing, a revision function for objects' time creation has been added:

bool RevisionCreateTime(int quant,datetime &time_create[])
  {
   datetime t=0;
   for(int i=quant-1;i>=0;i--)
     {
      t=time_create[i];
      if(t==0)
        {
         Print(LINE_NUMBER,__FUNCTION__,", Error create time: ",
               TimeToString(t,TIME_DATE|TIME_SECONDS));
         return(false);
        }
     }
//---
   return(true);
  }

This function's call is placed in the OnInit() after the PanelCreate().

I assume that if after the terminal's emergency closure and its subsequent restart this function returns false, then before it happens, an error message 4102 from the CreateTimeGet() function, indicating that the chart doesn't respond, may appear in the terminal's "Experts" tab.

4.1.6. In the block of the OnInit() function:

4.1.6.1. Before the function for creating control panels, the added semaphore flag is initialized with value -1. It means the alarm is disabled for the time of executing "friendly" actions with objects. In particular, to avoid unexpectancies when the chart's timeframe is changed.

flagAntivandal=-1;

4.1.6.2. The indicator's short name is set, if it hasn't been done yet. It will be needed for the program's forced removal from the chart.

In the provided test code example the indicator's short name has been previously set and was equal to the objects' prefix, consisting of the indicator's name, symbol and period of chart where it will be set:

//--- create prefix for names of code's objects and indicator's short name:
   StringConcatenate(prefixObj,NAME_INDICATOR,Symbol(),"_",EnumToString(Period()));
//--- set the indicator's short name:
   IndicatorSetString(INDICATOR_SHORTNAME,prefixObj);

4.1.6.3. We enable the "protection" alarm after the function of creation of control panel and revision of array with objects' creation time:

//--- create a panel:
   PanelCreate();
/*revision of array with time of creating objects, and terminating 
the program if something goes wrong:*/
   if(!RevisionCreateTime(QUANT_OBJ,timeCreateObj))
     {return(REASON_INITFAILED);}
/*we set the flag to the response position
to events of removing and changing objects:*/
   flagAntivandal=0;

4.1.6.4. We enable the notification about events of graphic objects' removal from the chart, if it is disabled in the properties:

//--- enable notification about events of deleting graphic objects
 bool res=true;
 ChartEventObjectDeleteGet(res);
 if(res!=true)
 {ChartEventObjectDeleteSet(true);}

Note: ChartEventObjectDeleteGet() and ChartEventObjectDeleteSet() are ready functions from the page with examples of working with charts in Documentation.

4.1.6.5. Before exiting OnInit() we set the frequency of timer in seconds to check, if notifications about events of deleting objects will be disabled in the chart properties during this program's operation:

EventSetTimer(5);

4.1.7. In the block of the OnDeinit() function:

We specify the "alarm" to be disabled:

flagAntivandal=-1;

We specify the termination of generating events from the timer:

EventKillTimer();

Deletion of the indicator if the code of REASON_INITFAILED deinitialization's reason comes from OnInit():

//--- deleting indicator if REASON_INITFAILED came from OnInit().
   if(reason==REASON_INITFAILED)
     {
      ChIndicatorDelete(prefixObj,textDelInd[0],textDelInd[1],0,0);
     }

4.1.8. In the block of the OnTimer() function:

Using copy and paste, replicate the code lines from the point 4.1.6.4 to enable notifications about events of deleting objects on the chart with a timer, in case they are disabled by another program.

4.1.9. Creating actions in the OnChartEvent() function:

4.1.9.1. If handling is provided only for "security measures" in the event of changing and deleting objects, then handling of these events can be combined into one block.

4.1.9.2. Then the first thing is to check the flag, if the "alarm" is enabled when receiving a notification about the event of changing/deleting object. If the flag's value is equal to -1 (the "alarm" is disabled), then it exits from the event handling block.

4.1.9.3. When the flag's value is equal to 0 ("alarm" is enabled), you can first set the check for matching the object's name obtained with a prefix of program's objects in the notification. The StringFind() function is used for this purpose, so there is no need to iterate the entire array of program's objects names with every notification about changes and removal of objects from the chart. In this case a prefix set in OnInit() is not short, and due to that the filter is better. Prefix formation is provided in p.4.1.6.2.

If there is no match by prefix, the exit from the event handling block takes place.

4.1.9.4. If there is a match by prefix, only then received event notification is checked for a complete match by a name with "protected" program's objects. When names fully match, a value of the protection flag is changed to -1 (disable), to avoid creating extra loops, when a program is deleted from the chart.

4.1.9.5. In case there is an object that became "ownerless" as a result of being renamed, a search of objects with the same creation time as the one with a notification is executed, with a further removal, if found.

4.1.9.6. Only then the program is removed from the chart.

Described actions created in the code:

else  if(id==CHARTEVENT_OBJECT_DELETE || id==CHARTEVENT_OBJECT_CHANGE)
     {
      if(flagAntivandal==-1)//ignore the event, if the "alarm is disabled"
        {return;}
      else if(flagAntivandal==0)//if the "alarm" is enabled,
        {
         //--- look for a match by prefix:
         findPrefixObject=StringFind(sparam,prefixObj);
         if(findPrefixObject==0)//if there is a match by prefix
           {
            string chartObject=sparam;
            findPrefixObject=-1;
            //---
            for(int i=QUANT_OBJ-1;i>=0;i--)
              {
               //--- actions when names fully match:
               if(StringCompare(chartObject,nameObj[i],true)==0)
                 {
                  //--- disable the alarm to avoid looping the removal operations:
                  flagAntivandal=-1;
/*remove objects whose creation time equals the creation time of deleted or
 changed object:*/
                  ObDelCreateTimeExcept0(timeCreateObj[i],0,0,OBJ_EDIT);
                  //--- remove indicator from the chart:
                  ChIndicatorDelete(prefixObj,textDelInd[0],textDelInd[1],0,0);
                  //---
                  ChartRedraw();
                  return;
                 }
              }//for(int i=QUANT_OBJ-1;i>=0;i--)
            return;
           }//if(findPrefixObject==0)
         return;
        }//if(flagAntivandal==0)
      return;
     }//if(id==CHARTEVENT_OBJECT_DELETE || id==CHARTEVENT_OBJECT_CHANGE)  

4.1.10. The ObDelCreateTimeExcept0() function for removing objects by the time of their creation:

The function's scheme for deleting objects by their creation time may look like this:

void ObDelCreateTimeExcept0(datetime &create_time,// time of creation
                            long chart_ID=0,// chart's identifier
                            int sub_window=-1,// subwindow's number
                            ENUM_OBJECT type=-1)// -1 = all types of objects
  {
/*exit from the function, if the object's time = 0(1970.01.01 00:00:00),
 so there is no need to delete manually or programmatically created objects, 
 whose creation time zeroed out after 
 the terminal was restarted, from the chart:*/
   if(create_time==0){return;}
   int quant_obj=0;
   quant_obj=ObjectsTotal(chart_ID,sub_window,type);
//---
   if(quant_obj>0)
     {
      datetime create_time_x=0;
      string obj_name=NULL;
      //---
      for(int i=quant_obj-1;i>=0;i--)
        {
         obj_name=ObjectName(chart_ID,i,sub_window,type);
         create_time_x=(datetime)ObjectGetInteger(chart_ID,obj_name,
                        OBJPROP_CREATETIME);
         //---
         if(create_time_x==create_time)
           {ObDelete(chart_ID,obj_name);}
        }
     }
   return;
  }
//+------------------------------------------------------------------+

It has a filter inside, so if the creation data of the "protected" object equals 0 (it means: 1970.01.01 00:00:00), then handling events on it should be discontinued. This is required to avoid deleting objects created manually or programmatically, whose time of creation zeroed out after the terminal was restarted, from the chart.

4.1.11. The ObDelete() function in the code above:

It is taken from the ObjectCreateAndSet library and has an error output provided when deleting objects:

bool ObDelete(long chart_ID,string name)
  {
   if(ObjectFind(chart_ID,name)>-1)
     {
      ResetLastError();
      if(!ObjectDelete(chart_ID,name))
        {
         Print(LINE_NUMBER,__FUNCTION__,", Error Code = ",
               GetLastError(),", name: ",name);
         return(false);
        }
     }
   return(true);
  }

4.1.12. Function for deleting an indicator:

//+------------------------------------------------------------------+
//| Deleting an indicator from the chart                             |
//+------------------------------------------------------------------+
void ChIndicatorDelete(const string short_name,//indicator's short name
                       const string &text_0,//text when there is an error deleting an indicator
                       const string &text_1,//text when an indicator is successfully deleted
                       const long  chart_id=0,// chart's identifier
                       const int   sub_window=-1// subwindow's number
                       )
  {
   bool res=ChartIndicatorDelete(chart_id,sub_window,short_name);
//---
   if(!res){Print(LINE_NUMBER,text_0,GetLastError());}
   else Print(LINE_NUMBER,text_1);
  }
//+------------------------------------------------------------------+

where LINE_NUMBER is a general text at the beginning of messages containing the code line number set in #define:

#define LINE_NUMBER    "Line: ",__LINE__,", "

Now let's move on to the next scheme.


4.2. "Self-recovery" of the program's objects

Creating the objects' self-recovery requires more serious attention at the arrangement and change of flags' values to avoid looping the program's operation.

The scheme is described based on a modification of the previous test_count_click_1 code. The full code can be viewed and tested, you can find it in the attached file named test_count_click_2.

4.2.1. Creating a file with this scheme in the code:

The file test_count_click_1 is saved under the name of test_count_click_2.

4.2.2. Variables declared beyond any functions in the scope of visibility to all parts of the program, i.e. before the block of the OnInit() function:

The indicator's name is replaced in #define for a subsequent internal substitution:

#define NAME_INDICATOR             "count_click_2: "

Another variable is added, this time for an auxiliary adaptive flag, in addition to the main simple flag described previously:

int flagAntivandal, flagResetEventDelete;

A variable is also created for the message's text before performing recovery operations:

string textRestoring;

4.2.3. The LanguageTerminal() function responsible for displaying texts of messages depending on the trading terminal's language:

It contains a message displayed before recovery operations in case of the unauthorized removal or change of panel's objects:

void LanguageTerminal()
  {
   string language_t=NULL;
   language_t=TerminalInfoString(TERMINAL_LANGUAGE);
//---
   if(language_t=="Russian")
     {
      textObj[3]="Да: ";
      textObj[4]="Нет: ";
      textObj[5]="Всего: ";
      //---
      textRestoring="Уведомление о страховом случае: объект ";
      //---
      StringConcatenate(textDelInd[0],"Не удалось удалиться индикатору: \"",
                        prefixObj,"\". Код ошибки: ");
      StringConcatenate(textDelInd[1],"Удалился с графика индикатор: \"",
                        prefixObj,"\".");
     }
   else
     {
      textObj[3]="Yes: ";
      textObj[4]="No: ";
      textObj[5]="All: ";
      //---
      textRestoring="Notification of the insured event: object ";
      //---
      StringConcatenate(textDelInd[0],"Failed to delete indicator: \"",
                        prefixObj,"\". Error code: ");
      StringConcatenate(textDelInd[1],
                        "Withdrew from chart indicator: \"",
                        prefixObj,"\".");
     }
//---
   return;
  }

I used this text assuming that the considered options act as a safety net in case of undesirable events.

4.2.4. In the block of the OnInit() function:

We initialize both flags with a value -1 (the alarm is completely disabled) before the function of object creation:

flagAntivandal = flagResetEventDelete = -1;

After the block of creation of panel's objects and a revision of the time array for creating objects — the flag's initialized with value 0 (enabling the "alarm"):

//--- create a panel:
   PanelCreate();
/*revision of array with time of creating objects, and terminating 
the program if something goes wrong:*/
   if(!RevisionCreateTime(QUANT_OBJ,timeCreateObj))
     {return(REASON_INITFAILED);}
/*set anti-vandal flags in the position of response to 
events of deleting and changing objects:*/
   flagAntivandal=flagResetEventDelete=0;

4.2.5. Let's proceed to the OnChartEvent() function:

We are going to change the previously existing block of handling notifications about events of changing and deleting objects to the one below. In the new block the actions follow the scheme which is also common for handling events of removing and changing objects:

  • First of all, check if the "protection" is enabled. If not, then exit from the event handling block is performed.
  • Then, if the auxiliary flag for resetting extra deletion events is higher than 0, incoming notifications about events of deleting and changing panel's objects are reset until the flag equals 0.
  • Only when primary and auxiliary flags are equal to zero, recovery actions are possible in case of unauthorized change or removal of objects.
else  if(id==CHARTEVENT_OBJECT_DELETE || id==CHARTEVENT_OBJECT_CHANGE)
     {
      if(flagAntivandal==-1){return;}
      else if(flagResetEventDelete>0)
        {
         findPrefixObject=StringFind(sparam,prefixObj);
         if(findPrefixObject==0)
           {
            findPrefixObject=-1;
/*resetting values of the events counter flag for subsequent prevention 
of lopping code's operation from the queue of built up events of removing objects
that appeared as a result of recovery actions:*/
            flagResetEventDelete=flagResetEventDelete-1;
            //---
            return;
           }
         return;
        }
      else if(flagAntivandal==0 && flagResetEventDelete==0)
        {
         //--- examine a match by prefix:
         findPrefixObject=StringFind(sparam,prefixObj);
         if(findPrefixObject==0)//if there is a match by prefix
           {
            string chartObject=sparam;//object's name in the event
            findPrefixObject=-1;
/*checking whether an object is "protected" and taking recovery 
            measures, if it is:*/
            RestoringObjArrayAll(chartObject,prefixObj,flagAntivandal,
                                     flagResetEventDelete,QUANT_OBJ,nameObj,
                                     timeCreateObj);
            return;
           }//if(findPrefixObject==0)
         return;
        }//else if(flagAntivandal==0 && flagResetEventDelete==0)
      return;
     }//else  if(id==CHARTEVENT_OBJECT_DELETE || id==CHARTEVENT_OBJECT_CHANGE)

Main recovery activities are created in the RestoringObjArrayAll() function. Several of such functions can be simultaneously applied in the block for different groups of objects. You can find a relevant example in the fifth chapter of this article. Object's name by event of deleting or changing falls under this function after a check by prefix, if it matches the "protected" objects.

4.2.6. The RestoringObjArrayAll() function.

It contains the following actions:

  • If the check for a complete match by name shows that the object by event is "protected", then:
    • the main flag of "alarm" obtains value -1 (the "alarm" is disabled for the time of "friendly" actions);
    • in case it was an event of deleting object, an auxiliary flag is set equal to the overall amount of "protected" objects minus one, because if the event for deleting objects is handled, it means that by this moment at least one object was deleted.
  • After these preliminary actions the remaining protected objects are deleted by prefix using the ObDeletePrefixFlag() function shown below, while the deleted objects are calculated. Based on this calculation the value of the auxiliary flag flagResetEventDelete is corrected, if necessary.

And since during the deletion of objects the deletion events appear, they will not be subsequently handled until the value of the auxiliary flag won't be reset to 0 as they are handled in this part of the code:

else if(flagResetEventDelete>0)
        {
         findPrefixObject=StringFind(sparam,prefixObj);
         if(findPrefixObject==0)
           {
            findPrefixObject=-1;
/*resetting values of events counter flag for subsequent prevention 
of lopping code's operation from the queue of built up events of removing objects
that appeared as a result of recovery actions:*/
            flagResetEventDelete=flagResetEventDelete-1;
            //---
            return;
           }
         return;
        }

//+---------------------------------------------------------------------------------------+
//|Function of objects' "self-recovery" on OnChartEvent()                                 |
//+---------------------------------------------------------------------------------------+
//|string sparam = object's name passed to OnChartEvent()                                 |
//|string prefix_obj = common prefix of protected objects                                 |
//|int &flag_antivandal = flag for enabling/disabling alarm                               |
//|int &flag_reset = flag for resetting events                                            |
//|int quant_obj = total amount of objects in the "protected"                             |
//|array                                                                                  |
//|string &name[] = array of names of "protected" objects                                 |
//|datetime &time_create[] = array of times of creating these objects                     |
//|int quant_obj_all=-1 total amount of recoverable objects                               |
//|of the program, >= quant_obj (if -1, then equals quant_obj)                            |
//|const long chart_ID = 0 chart's identifier                                             |
//|const int sub_window = 0 window's index                                                |
//|int start_pos=0 which position in the objects' name to start searching the prefix with |
//+---------------------------------------------------------------------------------------+
int RestoringObjArrayAll(const string sparam,
                         const string prefix_obj,
                         int &flag_antivandal,
                         int &flag_reset,
                         const int quant_obj,
                         string &name[],
                         datetime &time_create[],
                         int quant_obj_all=-1,
                         const long chart_ID=0,
                         const int sub_window=0,
                         const int start_pos=0
                         )
  {
//--- accept the results here:
   int res=-1;
/*Checking for correctness of the input of the amount of objects
   to external parameters of the function. The fact that wrong external parameters 
   of this function were entered during the creation of the code
   will become known only after the event of "alarm".
   This is why you have to make sure in advance that you
   set correct external parameters for this function when creating the code.*/
   if(quant_obj<=0)//if the specified amount of protected objects <= 0
     {
      Print(LINE_NUMBER,__FUNCTION__,
            ", Error Code. Wrong value: quant_obj =",quant_obj);
      return(res);
     }
   else   if(quant_obj>quant_obj_all && quant_obj_all!=-1)
     {
      Print(LINE_NUMBER,__FUNCTION__,
            ", Error Code. Not the correct value: quant_obj_all");
      return(res);
     }
   else if(quant_obj_all==-1){quant_obj_all=quant_obj;}
//--- variable for a comparison value of a full object's name:
   int comparison=-1;
/*loop for checking the name of the object obtained from event with names in 
 the array of protected objects:*/
   for(int i=quant_obj-1;i>=0;i--)
     {
      comparison=StringCompare(sparam,name[i],true);
      //--- actions when names fully match:
      if(comparison==0)
        {
         comparison=-1;
         res=1;
         //--- notification about the occurrence of "insurance" case:
         Print(LINE_NUMBER,textRestoring,sparam);
/*while the program restores objects, we set the flag 
not to react to the events of deleting objects:*/
         flag_antivandal=-1;
/*Initial value of counter flag of the events that are no longer required 
for the further event handling. For the consequent prevention 
of the code's looping from the queue of built up events of deleting
objects as a result of recovery actions:*/
         flag_reset=quant_obj_all-1;
         //--- removing remaining protected objects of an array, if any:
         ObDeletePrefixFlag(flag_reset,prefix_obj,chart_ID,sub_window,start_pos);
         //--- redrаwing the chart:
         ChartRedraw();
         //--- removing renamed objects, if any:
         ObDelCreateTimeExcept0(time_create[i],chart_ID,sub_window);
         //--- recreation of objects:
         PanelCreate();
         //--- final redrawing of the chart:
         ChartRedraw();
         //--- enabling the alarm:
         flag_antivandal=0;
         //---
         return(res);
        }
     }//for(int i=QUANT_OBJECTS-1;i>=0;i--)
   return(res);
  }
Note: The function has the selection by the object type absent for a reason. Since during the objects' "self-recovery" the intention to decrease the amount of handling by narrowing the circle of handling by the object type can lead to the opposite effect, if during self-recovery all deleted and recreated panel's objects would be of different types. Therefore, from my point of view, it is best to not apply the strategy of narrowing the handling circle by a specific type of objects, if at the point of unauthorized interference the panel's objects on the chart consist of various types. And assuming that there may be those who are going to ignore this warning, the option is removed from the above function.

You can view undesirable effects if you write and use the selection by the objects' type during "self-recovery' in the example from the fifth chapter of the article, where objects of different types are present on the panel. You only have to enter Print() in the blocks of code for handling events and recovery in advance.

I should also add, that you can easily save resources by reducing the amount of handling in the code through entering restrictions by the object's type in the variant of the program's "self-departure" from the chart, as described above.

4.2.7. The ObDeletePrefixFlag() function is aimed to remove objects by prefix and, if necessary, correct the auxiliary flag's value:

I relied on a code's scheme used in the Sergei Kovalev's textbook, and which was afterwards mentioned in the MQL4 forum by Artem Trishkin and the user 7777877 unknown to me. I didn't pay much attention to it then, which is why I am grateful to those who brought it up in the forum, since the scheme's base is rather universal and has been of assistance to me in many different tasks.

Basically, its variety is applied here:

//+------------------------------------------------------------------+
//| int &flag_reset = flag for resetting events of deleting objects  |
//| string prefix_obj = general prefix in the objects' names         |
//| long  chart_ID = chart's identifier                              | 
//| int   sub_window=-1 = window's index (-1 = all)                  |
//| int start_pos=-1 = start position of a general prefix's          |
//| substring in the objects' name                                   |
//+------------------------------------------------------------------+
int ObDeletePrefixFlag(int &flag_reset,
                       string prefix_obj,
                       const long chart_ID=0,
                       const int sub_window=0,
                       int start_pos=0)

  {
   int quant_obj=ObjectsTotal(chart_ID,sub_window,-1);
   if(quant_obj>0)
     {
      int count=0;
      int prefix_len=StringLen(prefix_obj);
      string find_substr=NULL,obj_name=NULL;
//---
      for(int i=quant_obj-1;i>=0;i--)
        {
         obj_name=ObjectName(chart_ID,i,sub_window,-1);
         find_substr=StringSubstr(obj_name,start_pos,prefix_len);

         if(StringCompare(find_substr,prefix_obj,true)==0)
           {
            count=count+1;
            ObDelete(chart_ID,obj_name);
           }
        }
      if(count>0 && flag_reset<count)
        {flag_reset=count;}
     }
   return(flag_reset);
  }
The code's operation can be checked accordingly:
  • run the attached test code on the chart;
  • click the objects belonging to "Yes" and "No", increasing values from zero to another value;
  • and then try to change this panel's objects through the property dialog or by deleting them.
After execution of the automatic recovery actions the panel will have the "clicked" numbers again that were there at the time of changing and removing its objects, since the amount of clicks is saved in the data array, which is used when recreating the panel.

However, various responses to various objects can be created.

We have presented the main part of the procedure, except for some special cases, such as:

Now let's proceed to an example of a practical implementation of both ways the program responds to deleting and changing its objects.


5. Example of implementing two response options in one program

The selection of an indicator, where the implementation of two options of response reactions to unauthorized actions would be provided step by step, is not random:

And if necessary in the future, it can be extended by expanding the list of displayed values.

A full code is attached under the name of id_name_object. Here I will outline:

Purpose of a provided indicator: when clicking on any chart's object, it shows the name of the object clicked, its creation time and type (apart from its own). If necessary, you can copy these values from the fields where they are displayed. During an attempt to change or remove the displayed values from the panel their restoration is provided for. This goes separately from the options considered in the article, however, acts as an addition to them.

Furthermore, there are the following features:

The appearance of a control panel depending on the terminal's language

Fig. 7. The appearance of a control panel depending on the terminal's language

Map for arrays and a variable for storing the created names of panel's objects

Fig. 8. Map for arrays and variable for storing the created names of panel's objects


Name
Type of object
What is the purpose
Where is the text stored
Font
Color of text *
Background * Color of border *
Time of creation is stored
nameRectLabel
OBJ_RECTANGLE_LABEL
Canvas
---
--- ---
CLR_PANEL clrNONE
timeCreateRectLabel
nameButton[0]
OBJ_BUTTON
Button for removing identifier from the chart
textButtUchar[0]**
"Wingdings"
CLR_TEXT
CLR_PANEL clrNONE
timeCreateButton[0]
nameButton[1] OBJ_BUTTON
Button for "minimizing" panel
textButtUchar[1]**
"Wingdings"
CLR_TEXT
CLR_PANEL clrNONE
timeCreateButton[1]
nameButton[2] OBJ_BUTTON
Button for "maximizing" panel after "minimizing" it
textButtUchar[2]**
"Wingdings"
CLR_TEXT
CLR_PANEL clrNONE
timeCreateButton[2]
nameEdit0[0]
OBJ_EDIT
"Object's name:" label
textEdit0[0]
"Arial"
CLR_TEXT_BACK
CLR_PANEL CLR_BORDER
timeCreateEdit0[0]
nameEdit0[1]
OBJ_EDIT
"Creation time:" label textEdit0[1] "Arial"
CLR_TEXT_BACK
CLR_PANEL CLR_BORDER
timeCreateEdit0[1]
nameEdit0[2]
OBJ_EDIT
"Object's type:" label textEdit0[2] "Arial"
CLR_TEXT_BACK
CLR_PANEL CLR_BORDER
timeCreateEdit0[2]
nameEdit1[0]
OBJ_EDIT
Field for displaying object's name textEdit1[0]
"Arial"
CLR_TEXT
CLR_PANEL CLR_BORDER
timeCreateEdit1[0]
nameEdit1[1] OBJ_EDIT
Field for displaying time of creation textEdit1[1] "Arial"
CLR_TEXT
CLR_PANEL CLR_BORDER
timeCreateEdit1[1]
nameEdit1[2] OBJ_EDIT
Field for displaying object's type
textEdit1[2] "Arial"
CLR_TEXT
CLR_PANEL CLR_BORDER
timeCreateEdit1[2]

 

Table 2. Map for panel's objects

* Colors set with #define:

#define CLR_PANEL                  clrSilver//panel's general background
#define CLR_TEXT                   C'39,39,39'//main color of text
#define CLR_TEXT_BACK              C'150,150,150' //color of shadowed text 
#define CLR_BORDER                 clrLavender//colour of frames

** Symbols "Wingdings":

uchar textButtUchar[3]={251,225,226};


5.1. External variables:

The size of panel is calculated based on the size of one button. Therefore, the width and height of one button and font size are displayed in the external custom properties. This allows to increase the panel and texts displayed in it without making adjustments directly in the code.

External custom properties of an indicator

Fig. 9. External custom properties of an indicator


5.2. Variables for flags declared beyond any function in the scope of visibility to all parts of the program, i.e. until the block of the OnInit() function:

Two flags are declared:

int flagClick, flagResetEventDelete;

flagClick is a flag that sets tracking of notifications for clicks on the panel's buttons. Its values are:

0
Tracking notifications about events of clicking the panel's "minimization" buttons and removing identifier from the chart, i.e. this value is given, when the main part of the panel is on the chart.
-1
Is set before actions of "minimizing" or, on the contrary, "maximizing" the panel on the chart, and also prior to actions of deleting identifier from the chart after clicking on the corresponding button.
1 When tracking notifications about events of clicking on the "maximize" button.

 

Furthermore, it acts as the main "antivandal" flag (it was named flagAntivandal in the previous codes of this article) in case of the unauthorized modification or removal of objects. The value 0 enables the "alarm" of the panel's main part, 1 — of the "maximize" button, and -1 disables the "alarm" during ongoing actions, if any.

If you wish to create a main "antivandal" flag adaptive by value, as an auxiliary flag for resetting the event handling in the code, then a separate variable will be required for this purpose.

flagResetEventDelete is a flag used to reset the handling of unnecessary events with adaptive values when implementing the "self-recovery" option for changed or deleted objects.

Other variables declared in this area can be viewed in the attached code.


5.3. In the block of the OnInit() function:

int OnInit()
  {
   flagClick=flagResetEventDelete=-1;
/*array for panel's text fields where 
   values of the clicked graphic objects will be consequently displayed:*/
   for(int i=QUANT_OBJ_EDIT-1;i>=0;i--){textEdit1[i]=" ";}
//+------------------------------------------------------------------+
/*indicator's short name:*/
   StringConcatenate(prefixObj,INDICATOR_NAME,"_",Symbol(),"_",
                     EnumToString(Period()));
   IndicatorSetString(INDICATOR_SHORTNAME,prefixObj);
//--- creating object's name
   NameObjectPanel(nameRectLabel,prefixObj,"rl");
   NameObjectPanel(QUANT_OBJ_BUTTON,nameButton,prefixObj,"but");
   NameObjectPanel(QUANT_OBJ_EDIT,nameEdit0,prefixObj,"ed0");
   NameObjectPanel(QUANT_OBJ_EDIT,nameEdit1,prefixObj,"ed1");
//+------------------------------------------------------------------+
/*indicator's texts, depending on the trading terminal's language:*/
   LanguageTerminal();
//+------------------------------------------------------------------+
/*restrictions of one button's width and height:*/
   if(objWidthHeight>=20 && objWidthHeight<=300)
     {obj0WidthHeight=objWidthHeight;}
   else if(objWidthHeight>300){obj0WidthHeight=300;}
   else {obj0WidthHeight=20;}
//--- creating a control panel on the chart
   PanelCreate();
/*revision of arrays with time of creating objects and terminating the program,
if something goes wrong:
(only two out of three buttons are present in the panel's main part,
therefore their amount during revision is one less)*/
   if(!RevisionCreateTime(QUANT_OBJ_BUTTON-1,timeCreateButton))
     {return(REASON_INITFAILED);}
   if(!RevisionCreateTime(QUANT_OBJ_EDIT,timeCreateEdit0))
     {return(REASON_INITFAILED);}
   if(!RevisionCreateTime(QUANT_OBJ_EDIT,timeCreateEdit1))
     {return(REASON_INITFAILED);}
//---
   flagClick=flagResetEventDelete=0;
//--- including notifications about events of removing graphic objects:
   bool res;
   ChartEventObjectDeleteGet(res);
   if(res!=true)
     {ChartEventObjectDeleteSet(true);}
//--- create timer
   EventSetTimer(8);
//---
   return(INIT_SUCCEEDED);
  }

5.4. In the PanelCreate() function:

Panel's objects are divided into different name groups in order to simplify the change of its appearance in the future. The panel itself is placed in the top right corner of the chart. Accordingly, distances in pixels along axis X and Y determine the position of left top points of objects with respect to the top right corner of the chart.

In cases of unauthorized renaming of objects their time creation is set and recorded in the arrays provided for this purpose. A separate variable is provided for the panel's canvas.

void PanelCreate(const long chart_ID=0,//chart's ID 
                 const int sub_window=0)// subwindow's number
  {
//--- width of the panel's canvas:
   int widthPanel=(obj0WidthHeight*11)+(CONTROLS_GAP_XY*2);
//--- height of the panel's canvas:
   int heightPanel=(obj0WidthHeight*6)+(CONTROLS_GAP_XY*8);
//--- distance along the axis х:
   int x_dist=X_DISTANCE+widthPanel;
//---
   if(ObjectFind(chart_ID,nameRectLabel)<0)//canvas
     {
      RectLabelCreate(chart_ID,nameRectLabel,sub_window,x_dist,
                      Y_DISTANCE,widthPanel,heightPanel,"\n",CLR_PANEL,
                      BORDER_RAISED,CORNER_PANEL,clrNONE,STYLE_SOLID,1,
                      false,false,true);
      //--- determine and record time of creating object:
      CreateTimeGet(chart_ID,nameRectLabel,timeCreateRectLabel);
     }
//---
   x_dist=X_DISTANCE+CONTROLS_GAP_XY;
   int y_dist=Y_DISTANCE+CONTROLS_GAP_XY;
//---
   for(int i=QUANT_OBJ_BUTTON-2;i>=0;i--)
     {
      //--- buttons for deleting and minimizing the indicator:
      if(ObjectFind(chart_ID,nameButton[i])<0)
        {
         ButtonCreate(chart_ID,nameButton[i],sub_window,
                      x_dist+(obj0WidthHeight*(i+1)),y_dist,obj0WidthHeight,
                      obj0WidthHeight,CORNER_PANEL,
                      CharToString(textButtUchar[i]),"\n","Wingdings",
                      font_sz+1,CLR_TEXT,CLR_PANEL,clrNONE);
         //--- determine and record time of creating object:
         CreateTimeGet(chart_ID,nameButton[i],timeCreateButton[i]);
        }
     }
//---
   x_dist=X_DISTANCE+widthPanel-CONTROLS_GAP_XY;
   int y_dist_plus=(CONTROLS_GAP_XY*2)+(obj0WidthHeight*2);
//---
   for(int i=QUANT_OBJ_EDIT-1;i>=0;i--)
     {
      //--- names of object's properties:
      if(ObjectFind(chart_ID,nameEdit0[i])<0)
        {
         EditCreate(chart_ID,nameEdit0[i],sub_window,x_dist,
                    y_dist+(y_dist_plus*i),obj0WidthHeight*8,
                    obj0WidthHeight,textEdit0[i],"Arial",font_sz,
                    "\n",ALIGN_CENTER,true,CORNER_RIGHT_UPPER,
                    CLR_TEXT_BACK,CLR_PANEL,CLR_BORDER);
         //--- determine and record time of creating object:
         CreateTimeGet(chart_ID,nameEdit0[i],timeCreateEdit0[i]);
        }
     }
//---
   y_dist=Y_DISTANCE+obj0WidthHeight+(CONTROLS_GAP_XY*2);
//---
   for(int i=QUANT_OBJ_EDIT-1;i>=0;i--)
     {
      //--- for displaying texts of objects' property values:
      if(ObjectFind(chart_ID,nameEdit1[i])<0)
        {
         EditCreate(chart_ID,nameEdit1[i],sub_window,x_dist,
                    y_dist+(y_dist_plus*i),obj0WidthHeight*11,
                    obj0WidthHeight,textEdit1[i],"Arial",font_sz,
                    "\n",ALIGN_LEFT,true,CORNER_RIGHT_UPPER,
                    CLR_TEXT,CLR_PANEL,CLR_BORDER);
         //--- determine and record time of creating object:
         CreateTimeGet(chart_ID,nameEdit1[i],timeCreateEdit1[i]);
        }
     }
   return;
  }

5.5. In the OnDeinit():

"Disabling" the main flag for the time of deleting the indicator from the chart, deleting objects by prefix, stopping the event generation from the timer and removing the indicator from the chart, if a REASON_INITFAILED deinitialization reason is received from the OnInit():

void OnDeinit(const int reason)
  {
   flagClick=-1;
   ObjectsDeleteAll(0,prefixObj,0,-1);
   EventKillTimer();
//--- deleting indicator if REASON_INITFAILED is in OnInit.
   if(reason==REASON_INITFAILED)
     {
      ChIndicatorDelete(prefixObj,textDelInd[0],textDelInd[1],0,0);
     }
//---
   ChartRedraw();
  }

5.6. In the OnTimer():

Occasional checks by time set in OnInit(), if the notification about events of deleting graphic objects is included in the chart's properties:

void OnTimer()
  {
//--- including notifications about events of deleting graphic objects
   bool res;
   ChartEventObjectDeleteGet(res);
   if(res!=true)
     {ChartEventObjectDeleteSet(true);}
  }


5.7. The OnChartEvent() function:

5.7.1. In the block for handling notifications about events of clicking objects with a mouse:
If the flagClick semaphore flag equals 0 then:
  • First the checks are performed to find out whether this was a click on the buttons of the panel's main part.
  • If this was a click on the panel's minimization button, then the flagClick flag obtains value -1 prior to further actions.
  • If a "minimize" button was clicked, then the panel's main part is minimized and the "maximize" button is created in the top right corner of the chart.
  • The flagClick is given value of 1 after this, which implies tracking the clicks on the panel's "maximize" button and its unauthorized modification or removal.
  • If it was a click on the button for removing the indicator from the chart, then the flagClick flag is also given value -1 before any further actions. Only then an attempt is made to remove the indicator from the chart.
  • It if wasn't a click on the control panel's buttons, but on the other chart's object instead, except for this indicator's objects, then its name, time of creation and type are determined and displayed in this object's value panel.

If the flagClick semaphore flag equals 1, then by clicking the "maximize" button:

  • This flag is given value -1 prior to further actions.
  • The button is removed and the panel is created.
  • After that the flagClick flag is given value 0. The auxiliary flagResetEventDelete flag for resetting events of deleting "maximize" button obtains value 1.
if(id==CHARTEVENT_OBJECT_CLICK)//id = 1
     {
      if(flagClick==0)//if the control panel's main part is on the chart
        {
         string clickedChartObject=sparam;
         findPrefixObject=StringFind(clickedChartObject,prefixObj);
         //---
         if(findPrefixObject==0)
           {
            findPrefixObject=-1;
            //---
            for(int i=1;i>=0;i--)
              {
               if(StringCompare(clickedChartObject,nameButton[i],true)==0)
                 {
                  switch(i)
                    {
                     //--- "minimize" button:
                     case 1:flagClick=-1;
                     MinimizeTable(nameButton,2,textButtUchar,2,
                                   timeCreateButton,obj0WidthHeight);
                     flagClick=1;return;
                     //--- button for deleting an indicator from the chart:
                     case 0:flagClick=-1;
                     ChIndicatorDelete(prefixObj,textDelInd[0],textDelInd[1],0,0);
                     return;
                     default:return;
                    }
                 }
              }
           }
         else//if the click is not on buttons of the control panel,
           {
            SetValuesTable(clickedChartObject);//we set the object's value
            return;
           }
        }
      //--- if a click is on "maximize" panel:
      else if(flagClick==1)
        {
         findPrefixObject=StringFind(sparam,prefixObj);
         if(findPrefixObject==0)
           {
            string clickedChartObject=sparam;
            findPrefixObject=-1;
            //--- button of "maximize" panel:
            if(StringCompare(clickedChartObject,nameButton[2],true)==0)
              {
               flagClick=-1;
               //--- main actions when pressing the button to "maximize" panel
               ToExpandTheTable(clickedChartObject);
               //---
               flagClick=0;
               //--- for resetting the event of deleting this button:
               flagResetEventDelete=1;
               //---
               return;
              }
           }
         return;
        }
      return;
     }//if(id==CHARTEVENT_OBJECT_CLICK)
5.7.2. In the block for handling notifications about finishing the text editing in the Edit graphic object:
When there are values of graphic objects in the field of this panel, they become available for copying from these fields. Therefore, if values displayed by the indicator were changed or deleted when copying accidentally/intentionally in these fields, then the previous text is restored:
else  if(id==CHARTEVENT_OBJECT_ENDEDIT)//id = 3
     {
      findPrefixObject=StringFind(sparam,prefixObj);
      if(findPrefixObject==0)
        {
         string endEditChartObject=sparam;
         findPrefixObject=-1;
         int comparison=-1;
         //---
         for(int i=QUANT_OBJ_EDIT-1;i>=0;i--)
           {
            if(StringCompare(endEditChartObject,nameEdit1[i],true)==0)
              {
               string textNew=ObjectGetString(0,endEditChartObject,OBJPROP_TEXT);
               comparison=StringCompare(textEdit1[i],textNew,true);
               //---
               if(comparison!=0)
                 {
                  ObSetString(0,nameEdit1[i],OBJPROP_TEXT,textEdit1[i]);
                 }
               //---
               ChartRedraw();
               return;
              }
           }//for(int i=0;i<QUANT_OBJ_EDIT;i++)
         return;
        }//if(findPrefixObject==0)
      return;
     }//if(id==CHARTEVENT_OBJECT_ENDEDIT)
5.7.3. In the block for handling notifications about deleting or removing objects:
  • If the flagClick flag equals -1, then exit from the event handling block occurs, since its value is given to the flag during "friendly" actions.
  • If the first check is complete and it revealed that the flagResetEventDelete auxiliary flag for resetting events exceeds 0, and the object by event refers to the panel's objects, then events are reset and exited from the handling block.
  • If the flags flagClick and flagResetEventDelete are equal to 0, then, when the name of the object by event matches any "protected" object, recovery actions for the panel's main part are performed by using the already known RestoringObjArrayAll() function and the accompanying functions.
  • If the flagClick main flag equals 1 at the event, then when the name of the object by event matches the button to "maximize" the panel, the indicator is removed from the chart.

else  if(id==CHARTEVENT_OBJECT_DELETE || id==CHARTEVENT_OBJECT_CHANGE)
     {
      if(flagClick==-1)return;
      else if(flagResetEventDelete>0)
        {
         findPrefixObject=StringFind(sparam,prefixObj);
         if(findPrefixObject==0)
           {
            findPrefixObject=-1;
            flagResetEventDelete=flagResetEventDelete-1;
            //---
            return;
           }
         return;
        }
      //--- check if our object is deleted/removed:
      else if(flagClick==0 && flagResetEventDelete==0)
        {
         findPrefixObject=StringFind(sparam,prefixObj);
         //---
         if(findPrefixObject==0)
           {
            string chartObject=sparam;
            findPrefixObject=-1;
            int res=-1;
            //--- actions if object is the control panel's canvas:
            res=RestoringObjOneAll(chartObject,prefixObj,flagClick,
                                   flagResetEventDelete,nameRectLabel,
                                   timeCreateRectLabel,QUANT_OBJ_ALL);
            //---
            if(res==1){return;}
            //--- actions if object is the control panel's button:
            res=RestoringObjArrayAll(chartObject,prefixObj,flagClick,
                                     flagResetEventDelete,QUANT_OBJ_BUTTON,
                                     nameButton,timeCreateButton,QUANT_OBJ_ALL);
            //---
            if(res==1){return;}
            //--- actions if object is a note (Edit0) on the control panel:
            res=RestoringObjArrayAll(chartObject,prefixObj,flagClick,
                                     flagResetEventDelete,QUANT_OBJ_EDIT,
                                     nameEdit0,timeCreateEdit0,QUANT_OBJ_ALL);
            //---
            if(res==1){return;}
            //--- actions if object is a note (Edit1) of the control panel:
            res=RestoringObjArrayAll(chartObject,prefixObj,flagClick,
                                     flagResetEventDelete,QUANT_OBJ_EDIT,
                                     nameEdit1,timeCreateEdit1,QUANT_OBJ_ALL);
            //---
            return;
           }
        }
      else if(flagClick==1)//if the button of the minimized panel is changed or deleted
        {
         string clickedChartObject=sparam;
         if(StringCompare(clickedChartObject,nameButton[2],true)==0)
           {
            flagClick=-1;
            //--- removing the indicator from the chart:
            ChIndicatorDelete(prefixObj,textDelInd[0],textDelInd[1],0,0);
            return;
           }
         return;
        }
      return;
     }//else  if(id==CHARTEVENT_OBJECT_DELETE || id==CHARTEVENT_OBJECT_CHANGE)
The new feature in this block is the RestoringObjOneAll() function applied when the panel's canvas is changed or deleted. It is similar to the RestoringObjArrayAll() function present here and previously used.
//+-----------------------------------------------------------------------+
//|string sparam = object's name passed to OnChartEvent()                 |
//|string prefix_obj = common prefix of protected objects                 |
//|int &flag_antivandal = flag for enabling/disabling alarm               |
//|int &flag_reset = flag for resetting events                            |
//|string name = object's name                                            |
//|datetime time_create = time of object's creation                       |
//|int quant_obj_all = total number of recoverable objects                |
//|of the program, >= quant_obj (if -1, then equals quant_obj)            |
//|const long chart_ID = 0 chart's identifier                             |
//|const int sub_window = 0 window's index                                |
//|int start_pos=0 the position in the name of objects to look for prefix |
//+-----------------------------------------------------------------------+
int RestoringObjOneAll(const string sparam,
                       const string prefix_obj,
                       int &flag_antivandal,
                       int &flag_reset,
                       string name,
                       datetime time_create,
                       const int quant_obj_all,
                       const long chart_ID=0,
                       const int sub_window=0,
                       int start_pos=0
                       )
  {
   int res=-1;
   int comparison=-1;
//---
   comparison=StringCompare(sparam,name,true);
//--- actions when names fully match:
   if(comparison==0)
     {
      res=1;
      comparison=-1;
      //--- notification about the occurrence of "insurance" case:
      Print(LINE_NUMBER,textRestoring,sparam);
      //---
      flag_antivandal=-1;
      flag_reset=quant_obj_all-1;
      //---
      ObDeletePrefixFlag(flag_reset,prefix_obj,chart_ID,sub_window,start_pos);
      //---
      ChartRedraw();
      //---
      ObDelCreateTimeExcept0(time_create,chart_ID,sub_window,-1);
      //---
      PanelCreate();
      //---
      ChartRedraw();
      //---
      flag_antivandal=0;
      //---
      return(res);
     }
   return(res);
  }

Full indicator's code is attached under the name: id_name_object.


6. Conclusion

From what you could see, easy schemes and their variations based on the flag system mentioned in this article can be added to the ready code in relation to many or one object, if there is such necessity or possibility. There may be no need to make considerable changes in the parts of the code that are responsible for the program's main operation.

If necessary, other relevant actions can be provided in the code for cases like:

For example, you can implement some additional checks during the "routine" actions on the panel, as well as selective or total revision checks on the important data displayed on the panel by timer, if it is justified with regard to the code.

In the Expert Advisor, for example, you can arrange animation during unauthorized changes or removal of graphic objects with or without displaying messages, if it will not cause a significant interference with the performance of trading activities.

When displaying messages you can, for instance, provide the possibility of granting the selection by pressing the relevant button: deleting the program from the chart or making an attempt to restore the damaged objects. Furthermore, you can provide a counter that may display the following warning in the situations of unauthorized removal or changing of objects: This a "XX"th unauthorized interference in the program's objects. After the "XXX"th case the program will delete itself from the chart. Please find out the possible reason of this situation".

Based on major opportunities of the MQL5 programming language and, certainly, your inquisitive mind and knowledge, you can create various options, including the ones that were not covered in this article, if needed.