Desenvolvimento - C#

Utilizando Checkbox em uma TreeView

De uma forma simples e estruturada, vamos ver como preencher e excluir dados de uma TreeView utilizando checkbox para seleção dos Nodes. Neste artigo, devemos também nos atentar a classe DBHelper.

por Alexandre Minato



Depois de apanhar um pouco com o evento SelectedNodeChanged do TreeView do ASP.NET descobri que o fato de você clicar no checkbox não dispara o evendo para o servidor. Procurei um pouco e encontrei este site que funcionou perfeitamente.

Publiquei este artigo que demostra como preencher um TreeView de forma recursiva utilizando LinqToSql, como este não é o foco, não vou entrar em detalhes sobre o preenchimento.

Primeiro vamos montrar a estrutura para exclusão, neste artigo é possível entender as propriedades ObjectTrackingEnabled e DeferredLoadingEnabled. Para a estrutura do Context utilizei uma técnica bem interessante deste livro (que aliás é excelente) que foi originado deste site

image

Criei uma tabela de segmentos que utiliza uma recursividade (ParentID) para preechimento e o grande problema era excluir os dados no TreeView.

Criei uma solução bastante simples para este problema. Habilitei o Checkbox no TreeView e passo uma lista de Ids que serão excluídas.

O primeiro passo é habilitar o TreeView e criar um botão para excluir os itens selecionados.

<asp:LinkButton ID="lk" runat="server" OnClick="Excluir" Text="Excluir" />
<asp:TreeView ID="tv" ShowCheckBoxes="All" runat="server" />

image

HTML

Resultado.

Agora vamos incluir o javascript no projeto que será resposável para selecionar os checkboxs.

<script type="text/javascript">
function OnCheckBoxCheckChanged(evt) {
    var src = window.event != window.undefined ? window.event.srcElement : evt.target;
    var isChkBoxClick = (src.tagName.toLowerCase() == "input" && src.type == "checkbox");
    if (isChkBoxClick) {
        var parentTable = GetParentByTagName("table", src);
        var nxtSibling = parentTable.nextSibling;
        if (nxtSibling && nxtSibling.nodeType == 1)//check if nxt sibling is not null & is an element node
        {
            if (nxtSibling.tagName.toLowerCase() == "div") //if node has children
            {
                //check or uncheck children at all levels
                CheckUncheckChildren(parentTable.nextSibling, src.checked);
            }
        }
        //check or uncheck parents at all levels
        CheckUncheckParents(src, src.checked);
    }
}
function CheckUncheckChildren(childContainer, check) {
    var childChkBoxes = childContainer.getElementsByTagName("input");
    var childChkBoxCount = childChkBoxes.length;
    for (var i = 0; i < childChkBoxCount; i++) {
        childChkBoxes[i].checked = check;
    }
}
function CheckUncheckParents(srcChild, check) {
    var parentDiv = GetParentByTagName("div", srcChild);
    var parentNodeTable = parentDiv.previousSibling;

    if (parentNodeTable) {
        var checkUncheckSwitch;

        if (check) //checkbox checked
        {
            var isAllSiblingsChecked = AreAllSiblingsChecked(srcChild);
            if (isAllSiblingsChecked)
                checkUncheckSwitch = true;
            else
                return; //do not need to check parent if any(one or more) child not checked
        }
        else //checkbox unchecked
        {
            checkUncheckSwitch = false;
        }

        var inpElemsInParentTable = parentNodeTable.getElementsByTagName("input");
        if (inpElemsInParentTable.length > 0) {
            var parentNodeChkBox = inpElemsInParentTable[0];
            parentNodeChkBox.checked = checkUncheckSwitch;
            //do the same recursively
            CheckUncheckParents(parentNodeChkBox, checkUncheckSwitch);
        }
    }
}
function AreAllSiblingsChecked(chkBox) {
    var parentDiv = GetParentByTagName("div", chkBox);
    var childCount = parentDiv.childNodes.length;
    for (var i = 0; i < childCount; i++) {
        if (parentDiv.childNodes[i].nodeType == 1) //check if the child node is an element node
        {
            if (parentDiv.childNodes[i].tagName.toLowerCase() == "table") {
                var prevChkBox = parentDiv.childNodes[i].getElementsByTagName("input")[0];
                //if any of sibling nodes are not checked, return false
                if (!prevChkBox.checked) {
                    return false;
                }
            }
        }
    }
    return true;
}
//utility function to get the container of an element by tagname
function GetParentByTagName(parentTagName, childElementObj) {
    var parent = childElementObj.parentNode;
    while (parent.tagName.toLowerCase() != parentTagName.toLowerCase()) {
        parent = parent.parentNode;
    }
    return parent;
}

</script>

Pronto! Camada de apresentação pronta, vamos preparar a estruta que será responsável pela exclusão.

image

Anexando a classe no dbml (LinqToSql)

Tem um
artigo que demonstra como criar o objeto relacional com o Linq.

Crie uma classe DBHelper.

public static class DbHelper
{   
    public static dbDadosDataContext getContextData(bool ObjectTrackingEnabled)
    {
        var db = getContextData();
        db.ObjectTrackingEnabled = ObjectTrackingEnabled;
        return db;
    }

    public static dbDadosDataContext getContextData(bool ObjectTrackingEnabled, bool DeferredLoadingEnabled)
    {
        var db = getContextData();
        db.ObjectTrackingEnabled = ObjectTrackingEnabled;
        db.DeferredLoadingEnabled = DeferredLoadingEnabled;
        return db;

    }

    public static dbDadosDataContext getContextData()
    {
        //Colocar a connectionString no web.Config
        string strCnn = ConfigurationManager.ConnectionStrings["cnnConnectionString"].ConnectionString;
        var db = new dbDadosDataContext(strCnn);
        db.DeferredLoadingEnabled = false;
        db.ObjectTrackingEnabled = false;
        return db;
    }

}

Agora vamos criar o método responsável pela exclusão.

public class Segmento : IDisposable
   {
       private dbDadosDataContext db;
       public Segmento()
       {
           db = DbHelper.getContextData();
       }

       public void ExcluirSegmentos(List<int> listSegments)
       {
           db.ObjectTrackingEnabled = true; // Habilito o tracking por haver manipulação no BD.
           var segs = db.SegmentoEntidades.Where(p => listSegments.Contains(p.IDSegmento));
           db.SegmentoEntidades.DeleteAllOnSubmit(segs);
           db.SubmitChanges();

       }

       public void PreencherTreeViewSegmento(TreeView objTreeView, int?
IDNoCarregar)
       {
           //A vantagem de utilizar o IQuarable é que a execução do Banco só será realizada no Bind.
           IQueryable<SegmentoEntidade> seg = db.SegmentoEntidades
                                              .Where(p => p.Inativo == false)
                                              .OrderBy(p => p.ParentID)
                                              .OrderBy(p => p.Segmento);

           //Limpo os nodes existentes.
          
objTreeView.Nodes.Clear();

           var itens = seg.Where(p => (p.ParentID == null ?
0 : p.ParentID) == (IDNoCarregar == null ? 0 : IDNoCarregar))
                          .OrderBy(p => p.ParentID)
                          .OrderBy(p => p.Segmento);

           //passo por todos os nodes que foi passado como parâmetro para carregar. (organizando em ordem alfabética
           foreach (var item in itens)
           {
               //Preencho o nó pai
               TreeNode nodePai = new TreeNode();
               nodePai.Value = item.IDSegmento.ToString();
               nodePai.Text = item.Segmento;
               nodePai.ImageToolTip = item.Segmento;
               //nodePai.ImageUrl = _db.IconeEntidades.Where(p => p.IDIcone == item.IDIcone).FirstOrDefault().urlImagem;
               //Preecho os filhos
               PreencherNoFilho(nodePai, seg);

               //Adiciono o nó que foi preenchido
               objTreeView.Nodes.Add(nodePai);
           }

       }
       protected void PreencherNoFilho(TreeNode parentNode, IQueryable<SegmentoEntidade> segmentos)
       {
           //Passo por todos os elementos filhos do nó pai
           var itens = segmentos.Where(p => p.ParentID == int.Parse(parentNode.Value)).OrderBy(p => p.ParentID)
                                .OrderBy(p => p.Segmento)

           foreach (var item in itens)
           {

               TreeNode noFiltro = new TreeNode();
               noFiltro.Value = item.IDSegmento.ToString();
               noFiltro.Text = item.Segmento;
               noFiltro.SelectAction = TreeNodeSelectAction.SelectExpand; //Expando no nó.
               parentNode.ChildNodes.Add(noFiltro); //adiciono na coleção de nós
               PreencherNoFilho(noFiltro, segmentos); //Preenchemos os filhos deste Node (recursividade)
           }


       }

       ~Segmento()
       {
           this.Dispose();
       }

       #region IDisposable Members

       public void Dispose()
       {
           GC.Collet();
           GC.SuppressFinalize(this);
       }

       #endregion
   }

Agora vamos criar os métodos na camada de apresentação.

Page_Load.

protected void Page_Load(object sender, EventArgs e)
{
    if (!Page.IsPostBack)
    {
        //Preencho o controle treeview
        FillControls(); //
        //atribuimos o evento click ao checkbox
        tv.Attributes.Add("onclick", "OnCheckBoxCheckChanged(event)");

    }

}

O método responsável para

Preencher o controle TreeView.

protected void FillControls()
{

    using (Segmento seg = new Segmento())
    {
        seg.PreencherTreeViewSegmento(tv, null);

    }

}

Agora vamos criar o método que irá excluir.

protected void Excluir(object sender, EventArgs e)
{
    List<int> lista = new List<int>();
    //Preencho a lista com os Nodes selecionados.

    foreach (TreeNode item in tv.CheckedNodes)
        lista.Add(int.Parse(item.Value));
   
    using (Negocio.Cliente.Segmento seg = new Negocio.Cliente.Segmento())
    {
        seg.ExcluirSegmentos(lista);
    }
    FillControls();
}

Pronto, de uma forma bem simples e estruturada preenchemos e excluimos itens com checkbox.

Espero que tenham gostado.

Sigam o @DicaDoNerd no Twitter.

Alexandre Minato

Alexandre Minato - Analista de Sistemas, atualmente trabalha em uma grande instituição financeira (Itaú-Unibanco), na área de mercado de renda variável (www.itautrade.com.br), com experiência de 7 anos na área de TI, inicialmente com VBA, VB, ASP clássico e há 3 anos com a platataforma .NET. (C# + ASP.NET).
Moderador de uma das maiores comunidades de Office System do Brasil (http://comunidade.itlab.com.br) e Participante em Fóruns da MSDN.
Website: www.alexandreminato.com.br