Palavras-chaves

TreeView, ASP.Net, Teclado, Setas, Javascript

Introdução

A treeview serve para navegar entre nós em uma estrutura de árvore divididos entre pais e filhos, essa estrutura é muito usada quando não se sabe a dimensão dos nós. Essa navegação só é disponível com o mouse no componente nativo do ASP.Net a partir da versão 2.0. O artigo mostra uma forma de adaptar a navegação em uma treeview do ASP.Net utilizando as setas do teclado e a tecla enter para ativar ou expandir o item através dos eventos Javascript, o que exige um trabalho maior para adaptar aos diversos browsers existentes.

Exemplo

O exemplo será feito no Visual Studio Team System 2008 e a linguagem C#. O exemplo também se aplica ao Visual Studio 2005 e VB.Net. Vamos aos passos para criar o projeto:

Passo 1

Abra o Visual Studio 2008, clique no menu Novo / Projeto, seleciona o tipo de projeto WEB e o template ASP.NET Web Application e chame sua aplicação de TesteTreeView. como mostra na Figura 1.

 
Figura 1 – Criando o projeto web

Passo 2

Na página default, que é aberta quando o projeto web é criado, acrescente os atributos ID e RUNAT na tag div com os respectivos valores divTree e Server, o resultado deve ficar como mostra a Figura 2. No exemplo tam´´em mudei a título da página para TesteTreeview.

 
Figura 2 – Adicionando o componente treeView na página Default.aspx

Passo 3

Na aba lateral esquerda ToolBox dentro na subdivisão Navigator arraste o controle TreeView para sua página Default.aspx entre as tags DIV. Somente para efeitos visuais escolhi um visual para a treeview, para selecionar um visual vá ao modo Design, localizado na parte inferior esquerda do Visual Studio 2008, seleciona a treeview e escolha um AutoFormat. Eu escolhi o modelo Simple. Ao final o código HTML deve estar como a Figura 3.

 
Figura 3 – HTML da página default com a treeview com o Autoformat Simple

Passo 4

Vamos adicionar uma pasta e criar o arquivo JavaScript que controlará os eventos na treeview. Na Solution Explorer do Visual Studio 2008, clique com o botão inverso no projeto, no menu Add clique em New Folder, como mostra a Figura 4.

 
Figura 4 – Adicionando nova pasta

Dê o nome da pasta de js, clique com o botão inverso na pasta js no menu Add clique em New Item, como mostra a Figura 5.

 
Figura 5 – Adicionando um novo arquivo a pasta js

Escolha o template JScript File e o nome do arquivo como EventosTree.js

 
Figura 6 – Criando o arquivo EventosTree.js dentro da pasta js

Adicione o código do Quadro 1 no arquivo criado.

function ClicouSetas(evt,tvNome)
{
    evt = getEvent(evt);
    var tecla = getKeyCode(evt);
    if (document.activeElement == null)
        return true;
    if (document.activeElement.tagName.toLowerCase() != 'a') //se não for o link
        return true;
    if (tecla != 37 && tecla != 38 && tecla != 39 && tecla != 40 && tecla != 13) //se as teclas não forem as SETAS e não for o ENTER sai do método
        return true;

    //TreeView_ToggleNode
    if (tecla == 13)//enter
        acionaItem(document.activeElement);
    else if (tecla == 39)//direita
    {
        var proximoItem = document.activeElement;
        var div_atual = proximoItem.parentNode.parentNode.parentNode.parentNode.parentNode.id;
        proximoItem = document.getElementById(tvNome + 't' + (parseInt(proximoItem.id.replace(tvNome + 't', '')) + 1).toString());
        if (proximoItem)
        {
            if (document.activeElement.href.indexOf('focus') != -1 || document.activeElement.href.indexOf('TreeView_ToggleNode') != -1)//se existe a função para expandir
            {
                if (proximoItem.parentNode.parentNode.parentNode.parentNode.parentNode.tagName.toLowerCase() == 'div'
                    && proximoItem.parentNode.parentNode.parentNode.parentNode.parentNode.style.display != 'none'
                    && proximoItem.parentNode.parentNode.parentNode.parentNode.parentNode.id != div_atual)
                {
                    proximoItem.focus();
                    //document.getElementById('ultimoFoco').value = proximoItem.id;
                }
                else
                    acionaItem(document.activeElement);
            }
        }
    }
    else if (tecla == 37)//esquerda
    {
        var proximoItem = document.activeElement;
        var div_atual = proximoItem;
        while (div_atual.tagName.toLowerCase()!='div')
            div_atual = div_atual.parentNode; //busca o div atual
        div_atual = div_atual.parentNode;
        while (div_atual.tagName.toLowerCase() != 'div')
            div_atual = div_atual.parentNode;//busca o div do pai
        if (proximoItem.href.indexOf('focus') != -1 || proximoItem.href.indexOf('TreeView_ToggleNode') != -1)//se o nó está expandido fechar
        {
            var dvp = proximoItem.href.split(',')[proximoItem.href.split(',').length - 1].replace('\'', '').replace('\'', '').replace('document.getElementById(', '').replace(')', '').replace(')', '');
            var proximo_div = document.getElementById(dvp); //qual o div que ele aciona
            if (proximo_div && proximo_div.style.display != 'none')
            {
                acionaItem(document.activeElement);
                return false;
            }
        }

        while (proximoItem != null)
        {
            proximoItem = document.getElementById(tvNome + 't' + (parseInt(proximoItem.id.replace(tvNome + 't', '')) - 1).toString());
            if (proximoItem
                && proximoItem.parentNode.parentNode.parentNode.parentNode.parentNode.style.display != 'none'
                && proximoItem.parentNode.parentNode.parentNode.parentNode.parentNode.id == div_atual.id)
            {
                try
                {
                    proximoItem.focus();
                    //document.getElementById('ultimoFoco').value = proximoItem.id;
                    break;
                } catch (ex) { continue; }
            }
        }
    }
    else if (tecla == 40)//baixo
    {
        var proximoItem = document.activeElement;
        while (proximoItem != null)
        {
            proximoItem = document.getElementById(tvNome + 't' + (parseInt(proximoItem.id.replace(tvNome + 't', '')) + 1).toString());
            if (proximoItem && proximoItem.parentNode.parentNode.parentNode.parentNode.parentNode.tagName.toLowerCase() == 'div' && proximoItem.parentNode.parentNode.parentNode.parentNode.parentNode.style.display != 'none')
            {
                try
                {
                    proximoItem.focus();
                    //document.getElementById('ultimoFoco').value = proximoItem.id;
                    break;
                } catch (ex) { continue; }
            }
        }
    }
    else if (tecla == 38)//cima
    {
        var proximoItem = document.activeElement;
        while (proximoItem != null)
        {
            proximoItem = document.getElementById(tvNome + 't' + (parseInt(proximoItem.id.replace(tvNome + 't', '')) - 1).toString());
            if (proximoItem && proximoItem.parentNode.parentNode.parentNode.parentNode.parentNode.tagName.toLowerCase() == 'div' && proximoItem.parentNode.parentNode.parentNode.parentNode.parentNode.style.display != 'none')
            {
                try
                {
                    proximoItem.focus();
                    //document.getElementById('ultimoFoco').value = proximoItem.id;
                    break;
                } catch (ex) { continue; }
            }
        }
    }
    return false;
}
function acionaItem(item)
{
    item.setAttribute('onclick', item.href);
    if (typeof (item.onclick) == 'function')
        item.onclick();
    else
        item.click();
    item.setAttribute('onclick', '');
    //document.getElementById('ultimoFoco').value = item.id;
}

//Recupera o código da tecla que foi pressionado
function getKeyCode(evt)
{
    var code;
    if (typeof (evt.keyCode) == 'number')
        code = evt.keyCode;
    else if (typeof (evt.which) == 'number')
        code = evt.which;
    else if (typeof (evt.charCode) == 'number')
        code = evt.charCode;
    else
        return 0;

    return code;
}
// recupera o evento do form
function getEvent(evt)
{
    if (!evt) evt = window.event; //IE
    return evt;
}

Quadro 1 – Código do arquivo EventosTree.js

Passo 5

Vamos adicionar a referência do arquivo javascript na página default.aspx. Para isso vá a página e acrescente a linha de códgio do Quadro 2 entre as tags HEAD e o resultado deve ficar como na Figura 7.

<script src="js/EventosTree.js" type="text/javascript"></script>

Quadro 2 – Referencia do arquivo javascript na página Default.apsx

 
Figura 7 – Referência do arquivo EventosTree.js na página Defulta.aspx

Passo 6

Agora vamos adicionar um código somente para preencher a treeview e setar o foco no primeiro elemento, você pode usar qualquer outro código ou banco de dados para preencher a sua treeview.
Na página Default.aspx, vá ao código da página apertando a tecla F7 e adicione o seguinte método do Quadro 3. Esse evento deve estar dentor da classe e fora do método Page_Load

        private void CarregarTV()
        {
            TreeView1.Nodes.Clear();
            TreeNode tr = new TreeNode();
            for (int i = 0; i < 3; i++)
            {
                tr = new TreeNode("Pai_" + i.ToString(), i.ToString());
                tr.SelectAction = TreeNodeSelectAction.Expand;
                for (int j = 0; j < 3; j++)
                    tr.ChildNodes.Add(new TreeNode("Filho_" + i.ToString() + "_" + j.ToString(), i.ToString() + "_" + j.ToString()));
                TreeView1.Nodes.Add(tr);
            }
            //Focus no primeiro elemento
            ClientScript.RegisterStartupScript(this.GetType(), "focoTreeView", "if(document.getElementById('" + TreeView1.ClientID + "t0'))document.getElementById('" + TreeView1.ClientID + "t0').focus();", true);
        }

Quadro 3 – Método em C# para preencher a treeview e setar o foco no primeiro elemento

Destaquei a linha do Quadro 3 que é um ponto interessante, nesse exemplo quando clicar com o mouse sobre um nó pai ele será expandido. A ultima linha do código seta o foco no primeiro elemento da treeview.

Passo 7

O último passo é ligar o div ao evento do js e chamar o método para carregar a treeview ao abrir a página. Para isso altere o evento Page_Load no código da página default como mostra o Quadro 4.

        protected void Page_Load(object sender, EventArgs e)
        {
            divTree.Attributes.Remove("onkeydown");
            divTree.Attributes.Add("onkeydown", "return ClicouSetas(event,'" + TreeView1.ClientID + "');");
            if (!IsPostBack)
                CarregarTV();
        }

Quadro 4 – Código do evento Page_Load da página default.aspx

Conclusão

O exemplo mostra uma forma de melhorar a forma de acesso às informações da página, melhorando a usabilidade para seus usuários. Isso é somente um passo, que faz grande diferença para seu produto. O resultado final é mostrado na Figura 8, ao abrir a página o foco inicial fica no primeiro elemento da treeview e as setas para baixo, cima, esquerda e direita já funcionam e a tecla enter aciona o item ou expande.

 
Figura 8 – Resultado final com a treeview navegada pelo teclado

Quem tiver alguma dúvida pode me mandar no e-mail tiago@grupodias.com.br
Fonte do exemplo no endereço http://cid-f8d073fe4fb32051.skydrive.live.com/self.aspx/P%c3%bablico/artigos/TesteTreeView.zip

Um grande abraço,
Tiago Silva Dantas Franco
Especialista em gestão da tecnologia da informação