Recentemente trabalhei em um projeto para Windows Phone onde a aplicação continha um mix de telas xaml e webviews (páginas web hospedadas nos servidores da empresa). O uso de webviews era um requisito da empresa pois as funcionalidades existentes nessas páginas não estavam disponíveis por meio de apis e tais apis não serão disponibilizadas no futuro próximo.

Para dar uma boa experiência para os usuários da aplicação, queríamos que o botão voltar do aparelho também afetasse a navegação do webview, fazendo com que o usuário não perceba que estã em uma webview e a aplicação inteira tenha um comportamento consistente. Já vi várias aplicações no Windows Phone que usam webviews sem fazer isso e a experiência é péssima pois você está no webview, usa o voltar do aparelho para tentar ir para a página anterior e acaba saindo da navegação do webview por completo e indo para a tela anterior ao webview (o aplicativo Audible é um exemplo desse comportamento ruim).

Para fazer essa integração com o botão voltar não basta assinar o evento do voltar do telefone e chamar o voltar do webbrowser pois é necessário saber se o webbrowser ainda tem para onde voltar e temos que tomar cuidado para não entrar em um looping de backstack (navegando entre páginas que foram chamadas usando o back, fazendo com que a pilha nunca acabe).

Para realizar isso de forma correta, precisamos controlar a navegação do webview. Esse controle consiste em criar e manter uma lista contendo as urls navegadas e um método que decide se o webview ainda pode voltar. A única limitação da solução proposta neste post é que ela só funciona corretamente para casos onde a navegação entre as páginas em modo webview passa parametros via url (querystring) e não via post. Se alguma página for navegada via post, o back pode não vai voltar para ela como o usuário espera. No caso da aplicação que eu estava trabalhando isso não seria um problema, mas se fosse seria necessária uma solução bem mais complexa que envolveria injeção de javascript nas páginas do webview.

O controle da navegação é feito assinando o evento “Navigated” do controle WebBrowser. Abaixo segue o código comentado usado nesse event handler.

int GoingBack = 0; //contador indicando se está voltando
Uri CurrentPage = null;
Stack<Uri> NavigationStack = new Stack<Uri>(); //pilha de navegação

///


/// conta quantas páginas foram navegadas para saber se pode voltar.
/// ignora a contagem quando a navegação foi feita usando back
///

void webview_Navigated(object sender, NavigationEventArgs e) {
    lock (this) {
        if (GoingBack > 0) {
            GoingBack--;
        } else {
            if (CurrentPage != null) {
                NavigationStack.Push(CurrentPage);//acrescenta a página de onde está saindo na pilha de navegação
            }
        }
        CurrentPage = e.Uri;
    }
}

O NavigationEventArgs do evento acima tem uma propriedade chamada NavigationMode que teoricamente indicaria se a navegação é do tipo New, Back, Forward ou Refresh, mas não está sendo usada pois ela não indicou os valores corretos nos meus testes, mesmo quando eu estava usando “history.back();” ou “history.go(-1);” via javascript injection no webview. Por isso foi necessário controlar se está sendo feita navegação de back e quais páginas estão na pilha.

O segundo método usado para esse processo é o responsável por tentar efetuar a navegação do webview para a página anterior e informar se conseguiu.

///


/// Tenta navegar o browser para a página anterior. Se conseguir retorna true, se não houver página anterior ou der erro, retorna false.
///

bool TryGoBack() {
    lock (this) {
        if (NavigationStack.Count > 0) {
            try {
                GoingBack++;
//incrementa o contador de navegação voltando
                webview.Navigate(NavigationStack.Pop()); //remove a última página navegada da pilha e navega para ela
                return true;
            } catch { }
        }
    }
    return false;
}

A última parte do processo todo é assinar o evento do botão back do telefone usando o código abaixo:

protected override void OnBackKeyPress(System.ComponentModel.CancelEventArgs e) {
    e.Cancel = TryGoBack();
    if (!e.Cancel) base.OnBackKeyPress(e);
}

Se sua aplicação tiver mais de um lugar que utilize webviews, você pode criar um controle para encapsular o webbrowser e os 2 primeiros métodos, mas o evento do botão voltar deverá ser assinado em cada página que usar esse controle, pois não é possível assinar o evento do voltar a partir de controle. Não esqueça de fazer o método TryGoBack ser public se encapsular o controle, para poder chamar a partir da página.

DISCLAIMER: Este código é disponibilizado “as is”, e atende às necessidades descritas acima, mas deve ser bem testado para garantir que funcionará da forma desejada em outras aplicações. Este código funciona tanto para Windows Phone 7 como 8.