Desperately seeking help on a CDialog problem -- mostly runs fine, but periodically becomes unusable

 

I have 30-40 hours studying and partially resolving this problem through ways that are not solving the problem, but trying to avoid it and/or reset it, with moderate but not complete success.

Anyone with experience dealing with nuances of instances of CAppDialog (combined with CChart etc.) Please have pity on me and help, even with just experience or ideas.

My instance CAppDialog as an EA is capable of running for hours, but sometimes will become completely hosed such as no button responses, or attempting to drag the dialog just scrolls the chart, the dialog is totally unresponsive.  Here are some common circumstances, my diagnosis of the genesis of the problem, and some hack based solutions which solved 90% of the problem.

I am 99% convinced that the problem is created when the chart event queue becomes overloaded.  Reasons for this conclusion:

1- I can often (but not always) cause the problem by waving the mouse around inside the dialog box which causes volumes of mousemove events to fire.

2 - I have a 250 millisecond timer firing to the log, and when the problem happens it is only printing to the log once every 2-10 seconds.  I have suspect events firing to the log, and they start coming through once every 2-10 seconds.

3 - The mousemove event in the Dialog.mqh library when executing these events is also executing the class's .Redraw() method which is targeting the main chart (chart id 0).  Removing that redraw code doesn't totally solve the problem, and makes the dragging/moving of the dialog box still functional but seriously delayed.

4 - One thing that made the problem significantly less frequent was having the timer re-execute the activation of the event handler with the following code:

ChartSetInteger(chartID,CHART_EVENT_OBJECT_CREATE,true);

5 - Manually changing the timeframe of the chart resolves the problem -- probably because a backed-up event queue gets immediately cleared?  The events I have logging instantly start working normally after changing timeframes.  Interestingly, when the dialog is functioning, its state is preserved via the .IniFileSave() and .IniFileLoad() functions, but when the dialog is hosed and the timeframe is changed, it reloads without its position and other state related settings having been saved and/or reloaded (it orients to the 0,0 corner of the chart which is a value that has never been stored to the .ini file.

6 - I have a script that when I drop on the form instantly recovers the dialog, its just one line of code.  However, when I trap for the timer saying "Hey, I haven't even fired since 10 seconds ago" (based on the TimeCurrent() when it last fired), executing that same line of code from within the EA does not resolve the problem.  That code is a simple null chart timeframe change:

ChartSetSymbolPeriod(0,NULL,0);

7 - Inside the timer I have checked if the dialog .IsActive() or .IsDisabled() and I would fix it if it is wrong, but my log shows they are continually =true even when the dialog is hosed.

8 - I have commented out some event handling from from my copy of the CDialog.mqh -- events which my dialog does not need, and it has helped but not eliminated the problem.

9 - The problem seems most common within the first minute or two of being dropped on the chart, but it can still happen after having run for quite awhile if I intentionally overload the event queue with mousemove or mouseclick events.  Sometimes it happens spontaneously just through normal use of the dialog, but in that case, usually when having been recently dropped on the form.

10 - Commenting out my timer (the timer which re-initializes the event handler) and then the problem happens all the time.  So the timer approach is hack-resolving a huge portion of the problem, but has nothing to do with truly resolving the source of the problem.

11 - Sometimes when the problem happens, it will even stop incoming ticks from coming into the chart.

12 - When the problem happens, the task manager shows the currency is taking up almost no processing.  1/10th or less than any other chart open in the trade station.  So when the problem happens the chart is using almost no resources -- it is not overloading resources.  And, at the time it happens, there is no other chart using an unusual amount of resources per the MT5 task manager.

13 - In the OnInit() I have executed a 2 second Sleep() after the dialog .Create() method (before the ea's OnInit() finishes) to attempt to allow the dialog some time to start up before the user can interact with it.  During this sleep time I .Deactivate() and .Disable() the dialog.  and reset them to true after the Sleep().  This has helped clear the most vulnerable time of starting up, but not resolved it entirely.

I am ABSOLUTELY PULLING MY HAIR OUT.  The darn thing works beautifully 90%-95% of the time.  But since its purpose is to be a trade entry manager it is critical that it doesn't freeze when the user is about to press the "Buy" or "Sell" button.

Out of desperation I see some other hack-approach solution, but am not aware that they are even possible:

- If I could get the EA to drop the script when it detects the problem, that would be a super-ugly hack to solve this, but I'm unaware of that being possible even as a last-ditch effort to move forward.

- I am unaware of a way to pro-actively in code clear the event queue.


I'm happy to answer any questions, and would greatly appreciate any expert commentary.  Thank you for reading this long post.

 
I haven't had that problem with CAppDialogs (other problems I did have that forced me to "mess" with the original source code) so I can only guess, but in your case I would first run it with the profiler to see where the program is more loaded
 
Manuel Alejandro Cercos Perez #:
I haven't had that problem with CAppDialogs (other problems I did have that forced me to "mess" with the original source code) so I can only guess, but in your case I would first run it with the profiler to see where the program is more loaded

Thank you for that suggestion.  I will try that next!!

I also changed the mousemove code that is necessary to drag the dialog box (which calls a redraw()), so that it only calls the redraw if the mouse is down.  that way at least waving the mouse around the dialog does not cause a flood of redraws, since I don't need to update the gui for waving the mouse over the dialog anyway.  It helps only theoretically LOL....

 
Christian Berrigan #:

Thank you for that suggestion.  I will try that next!!

I also changed the mousemove code that is necessary to drag the dialog box (which calls a redraw()), so that it only calls the redraw if the mouse is down.  that way at least waving the mouse around the dialog does not cause a flood of redraws, since I don't need to update the gui for waving the mouse over the dialog anyway.  It helps only theoretically LOL....

If you want to try things in the original library without risking too much you can try doing things like this:

//Put some macro in your program before you import the library
#define MY_PROGRAM_MACRO

//And in the library...
//...
#ifdef MY_PROGRAM_MACRO
        //Whatever changes in code you want to try
#else
        //What was originally (if you need it not to be executed with the changes above)
        //You can remove the #else if you're only adding code
#endif
//...

I've done things like that quite a few times (they're easier to find with a search too). Not the cleanest, but that gives some safety net if done right

 
Manuel Alejandro Cercos Perez #:

If you want to try things in the original library without risking too much you can try doing things like this:

I've done things like that quite a few times (they're easier to find with a search too). Not the cleanest, but that gives some safety net if done right

Hey Manuel,

First, let me thank you so much for getting my head out of my rear-end and going to the Coverage Profiler.

I *think* I have resolved my main problem (and also been able to get rid of the hack-type attempts to work around it, timers, resetting even handlers and all).

Let me share my solution for anyone else who may have a similar issue.

THE PROBLEM I IDENTIFIED:

I have a bunch of CEdit objects on the form, which store values in text, are read through the .Text() method, and assigned through .Text(newValue).  The coverage profiler told me that assigning a value to the text is no problem, but READING the text through .Text() sucks big time!  I was reading these values everywhere in my code (using the .Text() to store double numbers I'm processing on) and man, it was a disaster.

THE SOLUTION:

I subclassed the CEdit object, created a new double property m_value in the subclass, and changed all my code to read the new m_value property whenever I needed the value.  I created .AssignValue(double newVal) to change the new m_value property, and .GetValue() methods for accessing the m_value double values directly, and made the new .AssignValue(double newVal) method also assign its double value to the .Text().

Then I replaced every instance of .Text() in my code with .GetValue().

So far, it is working brilliantly.

This is the subclass I'm using so far.  It seems to work fine based on my current testing thus far.  Hope it helps someone, and I'm open to any additional suggestions:

//+------------------------------------------------------------------+
//| Class CCSBEditDouble                                                   |
//| Purpose: Subclass of CEdit that stores a double value                  |
//|            so don't have to retrieve .Text() to get value              |
//|            as coverage profiler suggests that is very time consuming   |
//|          Derives from class CEdit object.                              |
//+------------------------------------------------------------------+
class CCSBEditDouble: public CEdit
   {
public:
                     CCSBEditDouble(void);
                    ~CCSBEditDouble(void);

   //-- custom methods in this subclass for updating and acessing custom private properties
   //-- Assign the double value to the control and update the .Text() property
   bool     AssignValue(double newValue)        { m_value=NormalizeDouble(newValue,_Digits); Text(DoubleToString(newValue,_Digits)); return(true); }  
   double   GetValue(void)                      { return(m_value); } 


   //--- data access 
   // if assigning the text directly with a new string, update the m_value double property accordingly
   virtual bool              Text(const string value)       { if(NormalizeDouble(m_value,_Digits)!=StringToDouble(value)) m_value=StringToDouble(value); return(CWndObj::Text(value)); }
   

protected:
   
   
   //---- parent class handlers
   //--- handlers of object events
   
   //--- these have been adjusted to be used as subclassed methods
   //--- had to modify this parent method, otherwise, when directly editing contents of a control, the m_value still contained the old value and reset the .Text() back to the old value still in m_value
   virtual bool      OnObjectEndEdit(void)            { m_value=NormalizeDouble(Text(),_Digits); return(CEdit::OnObjectEndEdit()); }
   
   ////--- handlers of object settings, modified
   virtual bool        OnSetText(void)                  { bool returnVal=CEdit::OnSetText(); if(returnVal) m_value=StringToDouble(Text()); return(returnVal); }

private:

   //-- custom properties in this subclass
   //-- m_value will be used to assign actual double values instead of .Text(), 
   //--     and when the m_value is assigned through custom method .AssignValue() then the .Text() will be updated
   //--     by the custom .AssignValue() method.  Then, when the double value is needed for calculation
   //--     it can be accessed directly by custom .GetValue() method instead of reading .Text() and converting
   //--     which the coverage profiler says is really slow
   double m_value;
   
   };

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CCSBEditDouble::CCSBEditDouble(void)
  {
   CEdit::CEdit();  
  }
//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CCSBEditDouble::~CCSBEditDouble(void)
  {
  }


Now all my calculations access the CEdit values via .m_value instead of via .Text() and the difference is beyond dramatic.


Thanks again for your simple but critical suggestion for me to look to the coverage profiler for solutions.  You are a life-saver!





 
Nice! I'm glad that was helpful