--- name: mql5-dashboard-survive-tf-change description: > ทำให้ Dashboard edit fields และ runtime values อยู่รอดเมื่อ user เปลี่ยน timeframe (OnDeinit + OnInit re-run) โดยใช้ JSON file persistence. รวมถึงวิธีแก้ข้อจำกัดของ MQL5 ที่ห้ามมี input declarations ใน .mqh files. --- # MQL5 Dashboard: ทำให้ค่าบน Chart Objects อยู่รอดเมื่อเปลี่ยน Timeframe ## ปัญหา เมื่อ user เปลี่ยน timeframe บน MetaTrader, terminal จะ: 1. เรียก `OnDeinit(reason)` 2. เรียก `OnInit()` ใหม่ ถ้า `OnInit()` อ่านค่าจาก `input` parameters หรือ global variables ที่ไม่ได้ persist, ค่าที่ user พิมพ์ผ่าน dashboard edit fields จะ **หายหมด**เมื่อเปลี่ยน TF ## วิธีแก้: Persistent Storage ด้วย JSON File ### 1. บันทึกก่อน OnDeinit ```mql5 string MakeRuntimeFilePath() { string path = TerminalInfoString(TERMINAL_DATA_PATH); string safeSymbol = StringReplace(g_symbol, "/", "_"); return path + "\\MQL5\\Files\\SmartEntry_" + safeSymbol + "_runtime.json"; } bool SaveRuntimeValues() { string path = MakeRuntimeFilePath(); int handle = FileOpen(path, FILE_WRITE | FILE_ANSI | FILE_COMMON); if(handle == INVALID_HANDLE) return false; string json = StringFormat( "{\"upperZone\":%.5f,\"lowerZone\":%.5f,\"sl\":%.5f,\"tp\":%.5f,\"beOffset\":%d,\"isBuySetup\":%s}", g_upperZone, g_lowerZone, g_SL, g_TP, (int)g_beOffset, g_isBuySetup ? "true" : "false"); FileWriteString(handle, json + "\n"); FileClose(handle); return true; } void OnDeinit(const int reason) { SaveRuntimeValues(); // ← บันทึกก่อน cleanup ทุกครั้ง // ... release handles, destroy panel } ``` ### 2. โหลดกลับตอน OnInit ```mql5 bool LoadRuntimeValues() { string path = MakeRuntimeFilePath(); int handle = FileOpen(path, FILE_READ | FILE_ANSI | FILE_COMMON); if(handle == INVALID_HANDLE) return false; // ไม่มีไฟล์ = first run string json = FileReadString(handle); FileClose(handle); g_upperZone = StringToDouble(ParseJsonField(json, "upperZone")); g_lowerZone = StringToDouble(ParseJsonField(json, "lowerZone")); // ... return true; } int OnInit() { // Load ก่อน ถ้าไม่มีไฟล์ค่อย fallback ไป input defaults if(!LoadRuntimeValues()) { g_upperZone = InputUpperZone; // ... } // ... } ``` ### 3. Parse JSON field (MQL5 ไม่มี native JSON) ```mql5 string ParseJsonField(string json, string fieldName) { string search = "\"" + fieldName + "\":"; int pos = StringFind(json, search); if(pos < 0) return "0"; int start = pos + StringLen(search); while(start < StringLen(json) && json[start] == ' ') start++; if(json[start] == '"') { int end = start + 1; while(end < StringLen(json) && json[end] != '"') end++; return StringSubstr(json, start + 1, end - start - 1); } else { int end = start; while(end < StringLen(json) && json[end] != ',' && json[end] != '}' && json[end] != '\n') end++; return StringSubstr(json, start, end - start); } } ``` ## ข้อควรระวัง: `input` ใน .mqh File **MQL5 ไม่อนุญาต `input` declarations ใน .mqh include files** — compile error ### ผิด (compile error): ```mql5 // Dashboard.mqh input double InputUpperZone = 0; // ← ERROR: input not allowed in .mqh ``` ### ถูก: ```mql5 // Dashboard.mqh — ไม่มี input declarations ที่นี่ // สร้าง method ให้ EA เรียกหลัง Init void CDashboard::SetInitialEditTexts(double upperZone, double lowerZone, double sl, double tp, int beOffset) { if(upperZone > 0) ObjectSetString(0, m_prefix + "EREdit", OBJPROP_TEXT, DoubleToString(upperZone, _Digits)); // ... } // SmartEntryEA.mq5 g_dashboard.Init(g_symbol, true); g_dashboard.SetInitialEditTexts(g_upperZone, g_lowerZone, g_SL, g_TP, (int)g_beOffset); ``` ## ข้อควรระวัง: Chart Objects หายเมื่อ RecreatePanel `CreatePanel()` สร้าง objects ใหม่ทุกครั้งที่ `ObjectFind() < 0` check อยู่แล้ว — safe สำหรับ TF change เพราะ OnDeinit จะ `DestroyPanel()` ก่อน ## Symbols - `g_symbol` — ต้องกำหนดก่อนเรียก `MakeRuntimeFilePath()` - File path ใช้ `FILE_COMMON` flag เพื่อเขียนได้ทุก profile