using Newtonsoft.Json; using Steamware.Scheduler; using SteamWare.IO; using SteamWare.Logger; using System; using System.Collections.Generic; using System.ComponentModel; using System.Configuration; using System.Diagnostics; using System.Drawing; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; namespace IOB_MAN { public partial class IOBManPanel : Form { #region area gestione hide/maximize finestre private const int SW_SHOWNORMAL = 1; private const int SW_SHOWMINIMIZED = 2; private const int SW_SHOWMAXIMIZED = 3; [DllImport("user32.dll")] private static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow); #endregion /// /// Context x sync thread /// private readonly SynchronizationContext synchronizationContext; /// /// Totale processi avviati /// protected int numProcAvviati; /// /// Totale processi running /// protected int numProcRunning; /// /// Counter del timer di forceCheck /// protected int forceCheckPeriod = 5000; /// /// Counter del timer di base /// protected int checkPeriod = 1000; /// /// Counter del timer UI /// protected int uiPeriod = 200; /// /// Ms di attesa x uscita processo (std) /// protected int waitForExitMsec = 250; /// /// Elenco ARGS (uno per child da avviare) /// public List ArgsList = new List(); /// /// Binding source degli elementi gestiti.. /// private BindingSource ElencoIOB = new BindingSource(); /// /// Path dell'exe da chiamare /// protected string TargetExe = ""; /// /// Name dell'exe da chiamare /// protected string TargetName = ""; /// /// Contatore autockeck nativo /// protected int tOutAutocheck = 100; /// /// semaforo check... /// protected bool checkRunning = false; /// /// Oggetto locker x evitare problemi timer /// private static object _locker = new object(); /// /// Init classe /// public IOBManPanel() { InitializeComponent(); synchronizationContext = SynchronizationContext.Current; preInit(); loadConfig(); initTimers(); initControls(); updateStatus(); } private void initControls() { // gestione eventi binding source ElencoIOB.AddingNew += ElencoIOB_AddingNew; ElencoIOB.ListChanged += ElencoIOB_ListChanged; // collego tab a binding dgvManagedItems.DataSource = ElencoIOB; utils.lgInfo("Timer started"); if (utils.CRB("autoStartProc")) { apriChild(); utils.lgInfo("Start processi effettuato"); } } /// /// Inizializzazione timers /// private void initTimers() { MainTimer.Interval = checkPeriod; MainTimer.Start(); UI_Timer.Interval = uiPeriod; UI_Timer.Start(); forceCheckTimer.Interval = forceCheckPeriod; forceCheckTimer.Start(); // avvio il task con apposita classe Steamware... da 00:30 int fullRestartHour = memLayer.ML.CRI("fullRestartHour"); int fullRestartMinute = memLayer.ML.CRI("fullRestartMinute"); int fullRestartIntervMin = memLayer.ML.CRI("fullRestartIntervMin"); TaskSched.IntervalInMinutes(fullRestartHour, fullRestartMinute, fullRestartIntervMin, () => { // eseguo nel contesto di sincronizzazione synchronizationContext.Post(new SendOrPostCallback(o => { // effettuo reload conf e restart reloadConfAndRestart(); }), ""); }); } private void preInit() { utils.lgInfo("Starting App"); lblApp.Text = $"{ConfigurationManager.AppSettings.Get("appName")}"; lblVers.Text = $" v.{System.Reflection.Assembly.GetExecutingAssembly().GetName().Version}"; // init prog bar tsProgBar.Maximum = 100; tsProgBar.Step = 4; } /// /// Caricamento configurazione /// private void loadConfig() { ArgsList.Clear(); waitForExitMsec = utils.CRI("waitForExitMsec"); checkPeriod = utils.CRI("checkPeriod"); uiPeriod = utils.CRI("uiPeriod"); forceCheckPeriod = utils.CRI("forceCheckPeriod"); TargetExe = utils.CRS("targetExe"); TargetName = utils.CRS("appNameExt"); if (string.IsNullOrEmpty(TargetExe)) { //TargetExe = string.Format(@"{0}\Resources\Test.bat", Application.StartupPath); TargetExe = $@"{Application.StartupPath}\Resources\Test.bat"; } utils.lgInfo($"Target exe: {TargetExe}"); // caricamento configurazione argomenti di avvio processi loadArgList(); } private void loadArgList() { // in primis cerco SE ESISTA il file json di configuraizone parametri avvio string fileName = utils.CRS("ArgsConfFile"); string jsonFileName = $"{Application.StartupPath}{fileName}"; // verifico se esista il file richeisto if (File.Exists(jsonFileName)) { // leggo il file json StreamReader reader = new StreamReader(jsonFileName); string jsonData = reader.ReadToEnd(); if (!string.IsNullOrEmpty(jsonData)) { ArgsList = JsonConvert.DeserializeObject>(jsonData); } } else { // se non lo trovassi --> uso la chaive in web.config e GENERO un nuovo file x prox avvio... string ArgsString = utils.CRS("ArgsList"); utils.lgInfo($"Args found: {ArgsString}"); if (string.IsNullOrEmpty(ArgsString)) { var rand = new Random(); // ne creo rand (5-15) di default... for (int i = 0; i < rand.Next(5, 10); i++) { ArgsList.Add($"127.0.0.{i + 1}"); } } else { var elenco = ArgsString.Split(','); foreach (var item in elenco) { ArgsList.Add(item); } } // serializzo e salvo file! string jsonData = JsonConvert.SerializeObject(ArgsList); File.WriteAllText(jsonFileName, jsonData); } } private void ElencoIOB_ListChanged(object sender, System.ComponentModel.ListChangedEventArgs e) { } private void ElencoIOB_AddingNew(object sender, System.ComponentModel.AddingNewEventArgs e) { updateStatus(); } private void updateStatus() { synchronizationContext.Post(new SendOrPostCallback(o => { // aggiorno labels tsslNumProc.Text = $"Configurati {ArgsList.Count} processi | Avviati: {numProcAvviati} | Attivi: {numProcRunning}"; bool hlRestart = false; // colore da num proc... if (numProcRunning == ArgsList.Count) { tsslNumProc.ForeColor = Color.Green; } else if (numProcAvviati < ArgsList.Count) { tsslNumProc.ForeColor = Color.DarkRed; hlRestart = true; } else { tsslNumProc.ForeColor = Color.OrangeRed; hlRestart = true; } // fix autorestart... if (hlRestart) { chkAutoRestart.ForeColor = System.Drawing.Color.Red; chkAutoRestart.Text = "Auto Restart!!!"; txtTOutAutoCheck.Visible = true; btnMoreTOut.Visible = true; // se NON checked aggiorno contatore... if (!chkAutoRestart.Checked) { tOutAutocheck--; txtTOutAutoCheck.Text = $"{(tOutAutocheck / (1000 / (utils.CRI("checkPeriod"))))}"; } } else { chkAutoRestart.ForeColor = DefaultForeColor; chkAutoRestart.Text = "Auto Restart"; txtTOutAutoCheck.Visible = false; btnMoreTOut.Visible = false; } }), ""); } /// /// Apro un child x ogni args specificato /// private void apriChild() { // preventivamente CHIUDO TUTTO closeAllChild(true); Thread.Sleep(250); // avvio i child foreach (var item in ArgsList) { startChildProc(item); } numProcAvviati = ArgsList.Count; numProcRunning = numProcAvviati; } /// /// Avvio di un child process da parametro ARG /// /// /// private void startChildProc(string procArg, int indice = -1) { ProcessStartInfo psi = null; // da testare x aprire chiudere risorsa... psi = new ProcessStartInfo { FileName = TargetExe, Arguments = $"{utils.CRS("BaseArg")}{procArg}", WindowStyle = ProcessWindowStyle.Minimized }; //childProc.StartInfo = psi; Process p = Process.Start(psi); // accodo nuovo IOB... iobAdapt newIob = new iobAdapt(); DateTime adesso = DateTime.Now; newIob.redisMan = new RedisIobCache("", procArg); newIob.CodIOB = procArg; newIob.startTime = adesso; newIob.pID = p.Id; newIob.isRunning = true; // aggiungo a datasource, se indice -1 aggiungendo e basta, altrimenti alla posizione richiesta... if (indice == -1) { ElencoIOB.Add(newIob); } else { ElencoIOB.Insert(indice, newIob); } utils.lgInfo($"Avviato child process per {procArg} | pid: {p.Id}"); } /// /// Apro un child x fare udpate con parametro che impedisca avvio IOB /// private void apriOneUpdate() { ProcessStartInfo psi = null; // da testare x aprire chiudere risorsa... psi = new ProcessStartInfo { FileName = TargetExe, Arguments = "MODE=UPD IOB=NONE", WindowStyle = ProcessWindowStyle.Normal }; // avvio processo con using... using (Process p = Process.Start(psi)) { p.WaitForExit(); // ora chiudo current... SE configurato if (utils.CRB("closeOnChildUpdate")) { this.Close(); } } } /// /// Chiudo primo processo child (se ce ne sono) /// /// /// private void btnClose_Click(object sender, EventArgs e) { chiudiChildSel(); } private void chiudiChildSel() { int pid = -1; // SOLO SE selezionato in dgv... if (dgvManagedItems.SelectedRows.Count > 0) { foreach (DataGridViewRow riga in dgvManagedItems.SelectedRows) { // chiudo! _ = int.TryParse(riga.Cells["pID"].Value.ToString(), out pid); if (pid >= 0) { // provo a vedere SE ci sia il processo e di conseguenza lo chiudo... try { Process p = Process.GetProcessById(pid); // cerco e chiudo quelli che mi interessano... p.CloseMainWindow(); } catch { // errore era già chiuso.. } // indico NON running su datasource ((iobAdapt)ElencoIOB[riga.Index]).isRunning = false; } } } updateStatus(); } /// /// Cerca nell'elenco il processo corrente /// /// /// /// public static Process myGetProcByID(Process[] processlist, int id) { return processlist.FirstOrDefault(pr => pr.Id == id); } /// /// Effettua tutte le verifiche periodiche a timer... /// /// /// private void MainTimer_Tick(object sender, EventArgs e) { MainTimer.Stop(); if (!checkRunning) { //checkProcessStatusAsync(); try { Task result = checkProcessStatusAsync(); result.Wait(); } catch (Exception exc) { Logging.Instance.Error($"MainTimer_Tick {exc}"); } } MainTimer.Start(); } private void forceCheckTimer_Tick(object sender, EventArgs e) { if (!checkRunning) { //checkProcessStatusAsync(); try { Task result = checkProcessStatusAsync(); result.Wait(); } catch (Exception exc) { Logging.Instance.Error($"forceCheckTimer_Tick {exc}"); } } checkWatchdog(); // riavvio i timer x sicurezza... UI_Timer.Stop(); UI_Timer.Start(); MainTimer.Stop(); MainTimer.Start(); } private void UI_Timer_Tick(object sender, EventArgs e) { UI_Timer.Stop(); Task result = updateProgBarAsync(); result.Wait(); UI_Timer.Start(); } /// /// Controllo periodico dei processi DA RIATTIVARE /// private void checkWatchdog() { processAutoRestart(); // aggiorno datagrid! dgvManagedItems.Invalidate(); } /// /// Effettua processing autorestart /// private void processAutoRestart() { // verifico se ci siano processi (da ARGS LIST) NON running --> li riavvio! Dictionary proc2restart = new Dictionary(); Dictionary proc2close = new Dictionary(); int indice = 0; foreach (iobAdapt item in ElencoIOB.List) { // se NON comunica da troppo (ultima comunicazione è > 5 minuti fa...) if (!item.plcOk) { proc2close.Add(indice, item); } // se NON E' running if (!item.isRunning) { // segno da eliminare e riavviare proc2restart.Add(indice, item); } indice++; } // SE abilitato autorestart... if (chkAutoRestart.Checked) { // in primis processo quelli che non comunicano e mi limito a chiuderli... foreach (var item in proc2close) { ElencoIOB.Remove(item); utils.lgInfo($"Chiusura processo non in PLC-Online | IOB: {item.Value.CodIOB} | pid: {item.Value.pID}"); // chiudo! closeSingleChild(item.Value); } foreach (var item in proc2close) { // riavvio! startChildProc(item.Value.CodIOB, item.Key); } // se ho da riavviare... elimino! foreach (var dictVal in proc2restart) { ElencoIOB.Remove(dictVal.Value); utils.lgInfo($"Chiusura processo non in running | IOB: {dictVal.Value.CodIOB} | pid: {dictVal.Value.pID}"); } // li faccio ripartire! foreach (var item in proc2restart) { startChildProc(item.Value.CodIOB, item.Key); } // update! updateStatus(); } else { // se autorestart scaduto e NON checked --> lo imposto if (tOutAutocheck < 0) { // lo riattivo chkAutoRestart.Checked = true; } } } /// /// Controllo periodico dei processi attivi /// private async Task checkProcessStatusAsync() { var hasLock = false; try { Monitor.TryEnter(_locker, ref hasLock); if (!hasLock) { return; } if (!checkRunning) { // reset variabili appoggio checkRunning = true; // eseguo task! await Task.Run(() => checkRunningchild()).ConfigureAwait(false); updateStatus(); checkRunning = false; } } finally { if (hasLock) { Monitor.Exit(_locker); } } } private async Task updateProgBarAsync() { await Task.Run(() => performBarAdvance()).ConfigureAwait(false); } private void performBarAdvance() { synchronizationContext.Post(new SendOrPostCallback(o => { tsProgBar.PerformStep(); if (tsProgBar.Value >= tsProgBar.Maximum) { tsProgBar.Value = 0; } tsProgBar.Invalidate(); }), ""); } /// /// Verifica se i proc child siano ancora in RUN /// private void checkRunningchild() { List item2rem = new List(); IList allItems = (IList)ElencoIOB.List; bool needRem = false; numProcRunning = numProcAvviati; // 2020.02.01 passato chiamata specifica x leggere in 1 sola volta TUTTO elenco processi (x nome)... Process[] processList = Process.GetProcessesByName(TargetName); // ciclo Parallel.ForEach(allItems, item => { needRem = checkIstance(item, item2rem, processList); } ); // aggiorno datagrid! dgvManagedItems.Invalidate(); } private bool checkIstance(iobAdapt item, List item2rem, Process[] processList) { bool needRem; // verifico se esista il processo... try { if (processList.Length > 0) { Process p = myGetProcByID(processList, item.pID); needRem = p.HasExited; } else { needRem = true; } } catch { needRem = true; } if (needRem) { if (!item2rem.Contains(item)) { item2rem.Add(item); } item.isRunning = false; numProcRunning--; } else { item.isRunning = true; } return needRem; } private void IOBManPanel_FormClosing(object sender, FormClosingEventArgs e) { closeAllChild(true); Thread.Sleep(500); closeAllChild(true); closeTimers(); } private void closeTimers() { MainTimer.Dispose(); forceCheckTimer.Dispose(); UI_Timer.Dispose(); } /// /// Chiude il PID selezionato /// /// Chiude item richiesto private void closeSingleChild(iobAdapt item) { // rimuovo item ElencoIOB.Remove(item); try { Process p = Process.GetProcessById(item.pID); p.CloseMainWindow(); } catch { } } /// /// Chiude tutti i child /// /// resetta elenco private void closeAllChild(bool doReset) { List item2rem = new List(); foreach (iobAdapt item in ElencoIOB.List) { item2rem.Add(item); } // processod a elenco noto foreach (var item in item2rem) { if (item.isRunning) { try { Process p = Process.GetProcessById(item.pID); p.CloseMainWindow(); p.WaitForExit(waitForExitMsec); if (!p.HasExited) { utils.lgError($"Process not exited, now calling p.Kill()"); p.Kill(); } // indico NON running su datasource if (doReset) { ElencoIOB.Remove(item); } else { item.isRunning = false; } } catch (Exception exc) { Logging.Instance.Error($"Errore in fase di chiusura processo (pid: {item.pID} ) da elenco {exc}"); } } } // attendo 2*waitForExitMsec che i processi possano chiudersi gracefully... Thread.Sleep(waitForExitMsec * 2); forceKillByName(TargetName); // ripeto seconda volta x sicurezza Thread.Sleep(waitForExitMsec * 4); forceKillByName(TargetName); // verifico se resettare if (doReset) { // resetto elenco! ElencoIOB.Clear(); numProcAvviati = 0; } numProcRunning = 0; // update! updateStatus(); } /// /// Effettua un force kill dato nome processo /// /// private void forceKillByName(string nomeProc) { Process[] stillRunningProc = Process.GetProcessesByName(nomeProc); if (stillRunningProc != null) { if (stillRunningProc.Length > 0) { foreach (var item in stillRunningProc) { try { Process p = Process.GetProcessById(item.Id); { if (!p.HasExited) { p.Kill(); p.WaitForExit(waitForExitMsec); } if (!p.HasExited) { utils.lgError($"Process not Killed, 2nd try p.kill()"); p.Kill(); p.WaitForExit(waitForExitMsec * 2); } if (!p.HasExited) { utils.lgError($"Process not Killed, 3th try p.kill()"); p.Kill(); } } } catch (Exception exc) { Logging.Instance.Error($"Errore in fase di kill processo da nome {exc}"); } } } } } private void dgvManagedItems_SelectionChanged(object sender, EventArgs e) { checkButtons(); } /// /// verifica buttons attivi data selezione su gridview... /// private void checkButtons() { bool selected = (dgvManagedItems.SelectedRows.Count > 0); btnClose.Enabled = selected; } private void dgvManagedItems_CellDoubleClick(object sender, DataGridViewCellEventArgs e) { // seleziono riga... int pid = -1; if (e.RowIndex >= 0) { dgvManagedItems.Rows[e.RowIndex].Selected = true; using (var riga = dgvManagedItems.Rows[e.RowIndex]) { int.TryParse(riga.Cells["pID"].Value.ToString(), out pid); if (pid >= 0) { // provo a vedere SE ci sia il processo e di conseguenza lo chiudo... try { Process p = Process.GetProcessById(pid); // cerco e chiudo quelli che mi interessano... var windowsHandle = p.MainWindowHandle; ShowWindowAsync(windowsHandle, SW_SHOWNORMAL); } catch { // errore era già chiuso.. } } } } } private void btnMinimizeAll_Click(object sender, EventArgs e) { foreach (iobAdapt item in ElencoIOB.List) { if (item.isRunning) { try { Process p = Process.GetProcessById(item.pID); // cerco e chiudo quelli che mi interessano... var windowsHandle = p.MainWindowHandle; ShowWindowAsync(windowsHandle, SW_SHOWMINIMIZED); } catch (Exception exc) { // errore era già chiuso.. utils.lgError($"Errore in HIDE windows:{Environment.NewLine}{exc}"); } } } } private void btnMaximixeAll_Click(object sender, EventArgs e) { foreach (iobAdapt item in ElencoIOB.List) { if (item.isRunning) { try { Process p = Process.GetProcessById(item.pID); // cerco e chiudo quelli che mi interessano... var windowsHandle = p.MainWindowHandle; ShowWindowAsync(windowsHandle, SW_SHOWNORMAL); } catch (Exception exc) { // errore era già chiuso.. utils.lgError($"Errore in SHOW windows:{Environment.NewLine}{exc}"); } } } } /// /// Chiama apertura + update status... /// /// /// private void openALLToolStripMenuItem_Click(object sender, EventArgs e) { } /// /// Chiama Restart (close/start) + update status... /// /// /// private void restartALLToolStripMenuItem_Click(object sender, EventArgs e) { // chiude tutto closeAllChild(false); apriChild(); updateStatus(); } private void loadConfToolStripMenuItem_Click(object sender, EventArgs e) { // per iscurezza chiudo tutto closeAllChild(true); Thread.Sleep(1000); // lettura conf file... loadConfig(); // apertura apriChild(); updateStatus(); } private void updateModeToolStripMenuItem_Click(object sender, EventArgs e) { // chiude tutte closeAllChild(true); Thread.Sleep(1000); updateStatus(); // apre solo 1 con conf "fake" x condurre update... apriOneUpdate(); } private void btnCloseAll_Click(object sender, EventArgs e) { // chiude tutto closeAllChild(false); //apriChild(); updateStatus(); } private void btnRestartAll_Click(object sender, EventArgs e) { // chiude tutto closeAllChild(true); apriChild(); updateStatus(); } private void btnOpenAll_Click(object sender, EventArgs e) { // per iscurezza chiudo tutto closeAllChild(true); Thread.Sleep(1000); // lettura conf file... loadConfig(); // apertura apriChild(); updateStatus(); } /// /// Ramo applicazione (x update) /// protected string branchName = "master"; /// /// URL stringa di UPDATE... /// protected string updateUrl { get { return string.Format("http://seriate.steamware.net:8083/SWS/MAPO/IOB-MAN/{0}/manifest.xml", branchName); } } private void AutoUpdater_ApplicationExitEvent() { utils.lgInfo("Chiusura IOB-WIN"); Thread.Sleep(100); // chiudo tutto closeAllChild(true); Thread.Sleep(1000); utils.lgInfo("Chiusura Applicazione"); // attendo 1 sec... Thread.Sleep(1000); // ESCO! Application.Exit(); } private void updateIOBWINToolStripMenuItem_Click(object sender, EventArgs e) { // chiude tutte closeAllChild(true); Thread.Sleep(1000); updateStatus(); // apre solo 1 con conf "fake" x condurre update... apriOneUpdate(); } private void chkAutoRestart_CheckedChanged(object sender, EventArgs e) { // se tolgo autorestart --> imposto NUOVA scadenza x forzare check tOutAutocheck = 60 * utils.CRI("autoRestartTimeoutMin") * (1000 / (utils.CRI("checkPeriod"))); // fa subito controllo riavvio... processAutoRestart(); } private void btnMoreTOut_Click(object sender, EventArgs e) { tOutAutocheck += 60 * utils.CRI("autoRestartTimeoutMin") * (1000 / (utils.CRI("checkPeriod"))); } private void txtTOutAutoCheck_TextChanged(object sender, EventArgs e) { } private void forceCloseALLToolStripMenuItem_Click(object sender, EventArgs e) { // per iscurezza chiudo tutto closeAllChild(true); Thread.Sleep(1000); updateStatus(); } private void reloadConfRestartToolStripMenuItem_Click(object sender, EventArgs e) { reloadConfAndRestart(); } /// /// Effettua chiusura completa + rilettura conf + riavvio /// private void reloadConfAndRestart() { // per iscurezza chiudo tutto closeAllChild(true); Thread.Sleep(1000); updateStatus(); // rileggo conf loadConfig(); Thread.Sleep(500); // riapro apriChild(); } private void dgvManagedItems_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e) { if (this.dgvManagedItems.Columns[e.ColumnIndex].Name == "iobOnline") { if (e.Value != null) { bool isOk = false; bool.TryParse(e.Value.ToString(), out isOk); if (!isOk) { e.CellStyle.BackColor = Color.Red; } else { e.CellStyle.BackColor = Color.White; } } } else if (this.dgvManagedItems.Columns[e.ColumnIndex].Name == "lastPlcRead") { if (e.Value != null) { DateTime adesso = DateTime.Now; DateTime lastRead = adesso.AddMinutes(-60); DateTime.TryParse(e.Value.ToString(), out lastRead); if (Math.Abs(adesso.Subtract(lastRead).TotalSeconds) > 180) { e.CellStyle.BackColor = Color.Red; } else if (Math.Abs(adesso.Subtract(lastRead).TotalSeconds) > 120) { e.CellStyle.BackColor = Color.OrangeRed; } else if (Math.Abs(adesso.Subtract(lastRead).TotalSeconds) > 60) { e.CellStyle.BackColor = Color.Yellow; } else if (Math.Abs(adesso.Subtract(lastRead).TotalSeconds) > 15) { e.CellStyle.BackColor = Color.YellowGreen; } else { e.CellStyle.BackColor = Color.White; } } } else if (this.dgvManagedItems.Columns[e.ColumnIndex].Name == "queueElLen") { if (e.Value != null) { int coda = 0; int.TryParse(e.Value.ToString(), out coda); if (coda > 0) { e.CellStyle.ForeColor = Color.Red; } else { e.CellStyle.ForeColor = Color.Green; } } } else if (this.dgvManagedItems.Columns[e.ColumnIndex].Name == "queueFlLen") { if (e.Value != null) { int coda = 0; int.TryParse(e.Value.ToString(), out coda); if (coda > 0) { e.CellStyle.ForeColor = Color.Red; } else { e.CellStyle.ForeColor = Color.Green; } } } else if (this.dgvManagedItems.Columns[e.ColumnIndex].Name == "isRunning") { if (e.Value != null) { bool isOk = false; bool.TryParse(e.Value.ToString(), out isOk); if (!isOk) { e.CellStyle.ForeColor = Color.Red; DataGridViewCellStyle currstyle = dgvManagedItems[0, e.RowIndex].Style; currstyle.ForeColor = Color.Red; dgvManagedItems[1, e.RowIndex].Style = currstyle; dgvManagedItems[2, e.RowIndex].Style = currstyle; dgvManagedItems[3, e.RowIndex].Style = currstyle; } } } } private void btnStartSel_Click(object sender, EventArgs e) { // riapro child (SOLO SE non era già aperto...) apriChildSel(); } private void apriChildSel() { // SOLO SE selezionato in dgv... if (dgvManagedItems.SelectedRows.Count > 0) { // ciclo su row selezionate foreach (DataGridViewRow riga in dgvManagedItems.SelectedRows) { // verifico che sia già chiuso... if (((iobAdapt)ElencoIOB[riga.Index]).isRunning == false) { startChildProc(((iobAdapt)ElencoIOB[riga.Index]).CodIOB, riga.Index); // rimuovo vecchia riga... ElencoIOB.RemoveAt(riga.Index); } } } updateStatus(); } } }