diff --git a/AgroBase/AgroBase/Forms/IHM/Operacao/Parametros/ucParametrosMapa.cs b/AgroBase/AgroBase/Forms/IHM/Operacao/Parametros/ucParametrosMapa.cs index 3c2e30864..f351fdf9b 100644 --- a/AgroBase/AgroBase/Forms/IHM/Operacao/Parametros/ucParametrosMapa.cs +++ b/AgroBase/AgroBase/Forms/IHM/Operacao/Parametros/ucParametrosMapa.cs @@ -46,7 +46,7 @@ namespace AgroBase.Forms.IHM.Operacao.Parametros op.Mapa = new MapasModel(); _mapa = op.Mapa; - _mapa.pnlMapa = pnlMapaWeb; + _mapa.DefinirPainelMapa(pnlMapaWeb); _mapa.RuasSelecionadasAlteradas += Mapa_RuasSelecionadasAlteradas; _mapa.MapaCarregado += Mapa_MapaCarregado; @@ -167,7 +167,7 @@ namespace AgroBase.Forms.IHM.Operacao.Parametros if (_mapa == null) return; - _mapa.pnlMapa = pnlMapaWeb; + _mapa.DefinirPainelMapa(pnlMapaWeb); /* * Esse método já abre o diálogo, pergunta o tipo @@ -341,7 +341,7 @@ namespace AgroBase.Forms.IHM.Operacao.Parametros if (object.ReferenceEquals(_mapa.pnlMapa, pnlMapaWeb)) { - _mapa.pnlMapa = null; + _mapa.DefinirPainelMapa(null); } _mapa = null; diff --git a/AgroBase/AgroBase/Forms/IHM/Operacao/ucOperacaoNavegacao.cs b/AgroBase/AgroBase/Forms/IHM/Operacao/ucOperacaoNavegacao.cs index f6b563c14..e5e047c83 100644 --- a/AgroBase/AgroBase/Forms/IHM/Operacao/ucOperacaoNavegacao.cs +++ b/AgroBase/AgroBase/Forms/IHM/Operacao/ucOperacaoNavegacao.cs @@ -43,15 +43,26 @@ namespace AgroBase.Forms.IHM.Operacao return; _mapasInicializados = true; - _mapaDinamico = new MapaDinamicoModel(formPai, pnlMapaDinamico); + + _mapaDinamico = + new MapaDinamicoModel( + formPai, + pnlMapaDinamico + ); if (op == null) return; - if (op?.Mapa == null) + if (op.Parametros?.Modo != Enums.ModoOperacao.MapaGPS) + { + MostrarMapaDinamico(); + return; + } + + if (op.Mapa == null) op.Mapa = new MapasModel(); - op.Mapa.pnlMapa = pnlMapaWeb; + op.Mapa.DefinirPainelMapa(pnlMapaWeb); CarregarMapaWeb(); } @@ -136,6 +147,12 @@ namespace AgroBase.Forms.IHM.Operacao if (op == null) return; + if (op.Parametros?.Modo != Enums.ModoOperacao.MapaGPS) + { + MostrarMapaDinamico(); + return; + } + MapasModel mapa = op.Mapa; if (mapa == null) @@ -188,6 +205,12 @@ namespace AgroBase.Forms.IHM.Operacao } + public void SolicitarAtualizacaoMapaWeb() + { + CarregarMapaWeb(); + } + + private void MostrarStatusAutonomia() { pnlAlertaAutonomia.Visible = false; diff --git a/AgroBase/AgroBase/Forms/IHM/frmOperacao.cs b/AgroBase/AgroBase/Forms/IHM/frmOperacao.cs index 6fa708874..5ba6dc31a 100644 --- a/AgroBase/AgroBase/Forms/IHM/frmOperacao.cs +++ b/AgroBase/AgroBase/Forms/IHM/frmOperacao.cs @@ -120,10 +120,9 @@ namespace AgroBase.Forms.IHM VoltarSolicitado(this, EventArgs.Empty); }; - _ucParametros.ParametrosSalvos += async delegate + _ucParametros.ParametrosSalvos += delegate { _ucNavegacao.AtualizarMapaDinamico(true); - await _ucNavegacao.AtualizarMapaWebAsync(); }; tabMovimentacao.Click += delegate { SelecionarAba(AbaOperacaoIHM.Movimentacao, true); }; @@ -152,6 +151,7 @@ namespace AgroBase.Forms.IHM case AbaOperacaoIHM.Navegacao: lblContextoAba.Text = "Mapa, GNSS, trajetória e autonomia por corredor"; ExibirConteudo(_ucNavegacao); + _ucNavegacao.SolicitarAtualizacaoMapaWeb(); break; case AbaOperacaoIHM.Parametros: lblContextoAba.Text = "Parâmetros da operação atual e configuração local"; diff --git a/AgroBase/AgroBase/Models/MapasModel.cs b/AgroBase/AgroBase/Models/MapasModel.cs index 8b83102db..8fe292dd9 100644 --- a/AgroBase/AgroBase/Models/MapasModel.cs +++ b/AgroBase/AgroBase/Models/MapasModel.cs @@ -8,6 +8,7 @@ using System; using System.Collections.Generic; using System.Drawing; using System.Linq; +using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; using static AgroBase.Models.Enums; @@ -60,6 +61,10 @@ namespace AgroBase.Models private TaskCompletionSource _navegacaoTask; + private readonly SemaphoreSlim _browserInitLock = new SemaphoreSlim(1, 1); + private readonly SemaphoreSlim _navigationLock = new SemaphoreSlim(1, 1); + + public MapasModel() { mapaService = new MapasService(); @@ -245,83 +250,279 @@ namespace AgroBase.Models private async Task InicializarBrowserAsync() { - if (_browserInicializado && browser != null && !browser.IsDisposed) - { + if (_disposed) return; + + if (pnlMapa == null) + { + throw new InvalidOperationException( + "O painel de mapa não foi definido."); } if (pnlMapa.InvokeRequired) { - throw new InvalidOperationException("InicializarBrowserAsync deve ser chamado " + "na thread da interface."); + throw new InvalidOperationException( + "InicializarBrowserAsync deve ser chamado na thread da interface."); } - browser = new WebView2(); - browser.Dock = DockStyle.Fill; - browser.Margin = Padding.Empty; - browser.Visible = false; - pnlMapa.Controls.Add(browser); - browser.BringToFront(); + await _browserInitLock.WaitAsync(); - await browser.EnsureCoreWebView2Async(); + try + { + /* + * Outra chamada pode ter inicializado enquanto esta + * aguardava o lock. + */ + if ( + _browserInicializado && + browser != null && + !browser.IsDisposed && + browser.CoreWebView2 != null + ) + { + return; + } - browser.CoreWebView2.Settings.AreDefaultContextMenusEnabled = false; - browser.CoreWebView2.Settings.AreDevToolsEnabled = true; - browser.CoreWebView2.Settings.IsStatusBarEnabled = false; - browser.CoreWebView2.WebMessageReceived -= CoreWebView2_WebMessageReceived; - browser.CoreWebView2.WebMessageReceived += CoreWebView2_WebMessageReceived; - browser.NavigationCompleted -= Browser_NavigationCompleted; - browser.NavigationCompleted += Browser_NavigationCompleted; - pnlMapa.Resize -= PnlMapa_Resize; - pnlMapa.Resize += PnlMapa_Resize; + /* + * Remove uma instância anterior incompleta, + * se por algum motivo ela existir. + */ + if (browser != null) + { + try + { + browser.NavigationCompleted -= + Browser_NavigationCompleted; - _browserInicializado = true; + if (browser.CoreWebView2 != null) + { + browser.CoreWebView2.WebMessageReceived -= + CoreWebView2_WebMessageReceived; + } + + if (browser.Parent != null) + { + browser.Parent.Controls.Remove(browser); + } + + if (!browser.IsDisposed) + { + browser.Dispose(); + } + } + catch + { + // Estamos descartando uma instância incompleta. + } + + browser = null; + } + + _browserInicializado = false; + + /* + * IMPORTANTE: + * + * inicializa em variável LOCAL. + * + * Assim nenhuma outra chamada consegue trocar + * a instância que estamos aguardando. + */ + var novoBrowser = new WebView2 + { + Dock = DockStyle.Fill, + Margin = Padding.Empty, + Visible = false + }; + + pnlMapa.Controls.Add(novoBrowser); + novoBrowser.BringToFront(); + + try + { + await novoBrowser.EnsureCoreWebView2Async(); + + /* + * A tela/modelo pode ter sido destruída + * durante o await. + */ + if (_disposed || novoBrowser.IsDisposed) + { + if (!novoBrowser.IsDisposed) + { + novoBrowser.Dispose(); + } + + return; + } + + var core = novoBrowser.CoreWebView2; + + if (core == null) + { + throw new InvalidOperationException( + "O WebView2 concluiu a inicialização, " + + "mas CoreWebView2 permaneceu indisponível."); + } + + core.Settings.AreDefaultContextMenusEnabled = false; + core.Settings.AreDevToolsEnabled = true; + core.Settings.IsStatusBarEnabled = false; + + core.WebMessageReceived -= + CoreWebView2_WebMessageReceived; + + core.WebMessageReceived += + CoreWebView2_WebMessageReceived; + + novoBrowser.NavigationCompleted -= + Browser_NavigationCompleted; + + novoBrowser.NavigationCompleted += + Browser_NavigationCompleted; + + pnlMapa.Resize -= + PnlMapa_Resize; + + pnlMapa.Resize += + PnlMapa_Resize; + + /* + * Só agora publicamos a instância + * completamente inicializada. + */ + browser = novoBrowser; + + _browserInicializado = true; + } + catch + { + if (novoBrowser.Parent != null) + { + novoBrowser.Parent.Controls.Remove( + novoBrowser); + } + + if (!novoBrowser.IsDisposed) + { + novoBrowser.Dispose(); + } + + throw; + } + } + finally + { + _browserInitLock.Release(); + } } private async Task NavegarParaMapaAsync() { - if (browser == null || browser.CoreWebView2 == null) + await _navigationLock.WaitAsync(); + + TaskCompletionSource navegacaoAtual = null; + + try { - return; + if (_disposed) + return; + + WebView2 browserAtual = browser; + + if ( + browserAtual == null || + browserAtual.IsDisposed || + browserAtual.CoreWebView2 == null + ) + { + return; + } + + _paginaCarregada = false; + + navegacaoAtual = + new TaskCompletionSource( + TaskCreationOptions.RunContinuationsAsynchronously + ); + + _navegacaoTask = navegacaoAtual; + + string uriComVersao = + new Uri(mapaService.CaminhoHtml).AbsoluteUri + + "?v=" + + DateTime.UtcNow.Ticks; + + browserAtual.CoreWebView2.Navigate(uriComVersao); + + Task timeout = + Task.Delay(TimeSpan.FromSeconds(10)); + + Task concluida = + await Task.WhenAny( + navegacaoAtual.Task, + timeout + ); + + if (concluida == timeout) + { + navegacaoAtual.TrySetCanceled(); + + throw new TimeoutException( + "O mapa não concluiu o carregamento no tempo esperado."); + } + + await navegacaoAtual.Task; } - - _paginaCarregada = false; - - _navegacaoTask = new TaskCompletionSource(); - - string uriComVersao = new Uri(mapaService.CaminhoHtml).AbsoluteUri + "?v=" + DateTime.UtcNow.Ticks; - - browser.CoreWebView2.Navigate(uriComVersao); - - Task timeout = Task.Delay(TimeSpan.FromSeconds(10)); - - Task concluida = await Task.WhenAny(_navegacaoTask.Task, timeout); - - if (concluida == timeout) + finally { - _navegacaoTask.TrySetCanceled(); - throw new TimeoutException("O mapa não concluiu o carregamento no tempo esperado."); - } + if ( + navegacaoAtual != null && + ReferenceEquals( + _navegacaoTask, + navegacaoAtual + ) + ) + { + _navegacaoTask = null; + } - await _navegacaoTask.Task; + _navigationLock.Release(); + } } private void Browser_NavigationCompleted(object sender, CoreWebView2NavigationCompletedEventArgs e) { - if (_navegacaoTask == null) + var navegacao = _navegacaoTask; + + if (navegacao == null) + return; + + if (!(sender is WebView2 browserEvento)) + return; + + if (browserEvento.IsDisposed) return; if (!e.IsSuccess) { - _navegacaoTask.TrySetException(new InvalidOperationException("Falha ao abrir o HTML do mapa. " + "Erro WebView2: " + e.WebErrorStatus)); + navegacao.TrySetException( + new InvalidOperationException( + "Falha ao abrir o HTML do mapa. " + + "Erro WebView2: " + + e.WebErrorStatus + ) + ); return; } _paginaCarregada = true; - browser.Visible = true; - browser.BringToFront(); - _navegacaoTask.TrySetResult(true); + browserEvento.Visible = true; + browserEvento.BringToFront(); + + navegacao.TrySetResult(true); } private async Task EnviarMapaParaJavascriptAsync() @@ -755,6 +956,48 @@ namespace AgroBase.Models } + public void DefinirPainelMapa(Panel painel) + { + if (_disposed) + return; + + pnlMapa = painel; + + if ( + painel == null || + painel.IsDisposed || + browser == null || + browser.IsDisposed + ) + { + return; + } + + if (painel.InvokeRequired) + { + painel.BeginInvoke( + new Action(DefinirPainelMapa), + painel + ); + + return; + } + + if (!ReferenceEquals(browser.Parent, painel)) + { + if (browser.Parent != null) + { + browser.Parent.Controls.Remove(browser); + } + + painel.Controls.Add(browser); + } + + browser.Dock = DockStyle.Fill; + browser.BringToFront(); + } + + public void Dispose() { if (_disposed) @@ -762,6 +1005,10 @@ namespace AgroBase.Models _disposed = true; + _browserInicializado = false; + _paginaCarregada = false; + _mapaEnviado = false; + if (pnlMapa != null) { pnlMapa.Resize -= diff --git a/AgroBase/AgroBase/Models/Operacoes/OperacaoModel.cs b/AgroBase/AgroBase/Models/Operacoes/OperacaoModel.cs index 56cab2fa5..e67ac73aa 100644 --- a/AgroBase/AgroBase/Models/Operacoes/OperacaoModel.cs +++ b/AgroBase/AgroBase/Models/Operacoes/OperacaoModel.cs @@ -1349,7 +1349,7 @@ namespace AgroBase.Models var ruasSelecionadas = parametros.RuasPercorrer ?? new List(); - if (parametros.Mapa != null) + if (parametros.Mapa != null && parametros.Modo == ModoOperacao.MapaGPS) { novaOp.Mapa = new MapasModel(); novaOp.Mapa.DefinirRuasSelecionadas(novaOp, ruasSelecionadas); diff --git a/AgroBase/AgroBase/Services/MapasService.cs b/AgroBase/AgroBase/Services/MapasService.cs index 9ca907675..3556990c9 100644 --- a/AgroBase/AgroBase/Services/MapasService.cs +++ b/AgroBase/AgroBase/Services/MapasService.cs @@ -867,6 +867,8 @@ namespace AgroBase.Services let camadaTrajetoriaPlanejada = null; let camadaRastroRover = null; let camadaRastroBase = null; + let camadaTrajetoGps = null; + let pontosTrajetoGps = []; let pontosRastroRover = []; let pontosRastroBase = []; let ultimaPosicaoBase = null;