欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

在Winform开发框架中使用DevExpress的TreeList和TreeListLookupEdit控件

程序员文章站 2022-07-05 10:50:45
DevExpress提供的树形列表控件TreeList和树形下拉列表控件TreeListLookupEdit都是非常强大的一个控件,它和我们传统Winform的TreeView控件使用上有所不同,我一般在Winform开发中根据情况混合使用这些控件,不过整体来看,基于DevExpress的TreeL... ......

devexpress提供的树形列表控件treelist和树形下拉列表控件treelistlookupedit都是非常强大的一个控件,它和我们传统winform的treeview控件使用上有所不同,我一般在winform开发中根据情况混合使用这些控件,不过整体来看,基于devexpress的treelist和treelistlookupedit表现相对比较好看一些,本篇随笔主要介绍这两个控件在实际winform项目中的使用处理。

1、devexpress的treelist控件使用

例如在菜单管理中,我们知道菜单一般情况下是层次节点的,我们为了直观显示的需要,一般把菜单用树列表控件进行展示,其中就会用到我们说的treelist控件,如下界面所示。

在Winform开发框架中使用DevExpress的TreeList和TreeListLookupEdit控件

其中treelist和一个输入searchcontrol来一起协同使用,可以提高界面的友好性,我们可以通过输入关键字进行节点的过滤显示。

在Winform开发框架中使用DevExpress的TreeList和TreeListLookupEdit控件

如输入过滤内容后查询过滤树列表节点,如下所示,这样可以给用户快速模糊检索指定的树节点。

在Winform开发框架中使用DevExpress的TreeList和TreeListLookupEdit控件

有了大概的感官认识,我们来了解下treelist控件的使用情况

在菜单界面的设计视图下,我们添加一个contextmenustrip的右键菜单控件,然后编辑一些菜单项目,如下界面所示。

在Winform开发框架中使用DevExpress的TreeList和TreeListLookupEdit控件

然后拖入一个图片集合的imagecollection控件,并为它增加一些控件图片(也可以保留为空,然后动态指定,如本例一样)

在Winform开发框架中使用DevExpress的TreeList和TreeListLookupEdit控件

初始化树列表,我们一般分为几个函数,一个是初始化树列表,一个是绑定查询过滤的处理,一个是把数据绑定到树列表上去,如下代码所示。

        private async void frmmenu_load(object sender, eventargs e)
        {
            //列表信息
            inittree();
            initsearchcontrol();
            await bindtree();
        }

上面使用了异步操作,我们一般也可以不用异步,这里根据情况处理吧。

初始化树列表的操作代码如下所示。

        /// <summary>
        /// 初始化树控件
        /// </summary>
        private void inittree()
        {
            this.tree.columns.clear();
//添加显示列
            this.tree.columns.add(new treelistcolumn { fieldname = "id", caption = "id" });//增加一个隐藏的字段,存储需要的id
            this.tree.columns.add(new treelistcolumn { fieldname = "name", caption = "菜单名称", width = 160, visibleindex = 0 });
            //设置树控件的层次关系及属性
            tree.keyfieldname = "id";
            tree.parentfieldname = "pid";
            this.tree.optionsbehavior.editable = false;
            this.tree.optionsview.enableappearanceoddrow = true;
            this.tree.optionsview.enableappearanceevenrow = true;
            this.tree.optionsdraganddrop.dragnodesmode = dragnodesmode.none;//不允许拖动

            //设置树的图标集合及逐级图标
            this.tree.selectimagelist = this.imagecollection1;
            this.tree.customdrawnodeimages += (object sender, customdrawnodeimageseventargs e) =>
            {
                //int maxcount = this.imagecollection1.images.count;
                //var index = e.node.level < maxcount ? e.node.level : 0;
                //e.selectimageindex = index;
                var id = string.concat(e.node.getvalue(id_fieldname));
                int index = 0;
                idindexdict.trygetvalue(id, out index);
                e.selectimageindex = index;
            };

            //初始化树节点选择事件
            this.tree.focusednodechanged += delegate (object sender, focusednodechangedeventargs e)
            {
                this.focusednodechanged();
            };
        }
        private async void focusednodechanged()
        {
            if (this.tree.focusednode != null)
            {
                isnormalsearch = false;
                await binddata();
            }
        }

初始化树列表的处理代码,我们还可以使用扩展函数进一步简化它,如下所示。

       /// <summary>
        /// 初始化树控件
        /// </summary>
        private void inittree()
        {
            this.tree.columns.clear();
            this.tree.optionsdraganddrop.dragnodesmode = dragnodesmode.none;//不允许拖动

            //控件扩展函数封装处理
            this.tree.createcolumn("name", "菜单名称", 160, true);
            this.tree.inittree("id", "pid", null, false, false);

            //设置树的图标集合及逐级图标
            this.tree.selectimagelist = this.imagecollection1;
            this.tree.customdrawnodeimages += (object sender, customdrawnodeimageseventargs e) =>
            {
                //int maxcount = this.imagecollection1.images.count;
                //var index = e.node.level < maxcount ? e.node.level : 0;
                //e.selectimageindex = index;
                var id = string.concat(e.node.getvalue(id_fieldname));
                int index = 0;
                idindexdict.trygetvalue(id, out index);
                e.selectimageindex = index;
            };

            //初始化树节点选择事件
            this.tree.focusednodechanged += delegate (object sender, focusednodechangedeventargs e)
            {
                this.focusednodechanged();
            };
        }

初始化查询控件searchcontrol的代码如下所示。

/// <summary>
/// 实现树节点的过滤查询
/// </summary>
private void initsearchcontrol()
{
    this.searchcontrol1.client = this.tree;
    this.tree.filternode += (object sender, filternodeeventargs e) =>
    {
        if (tree.datasource == null)
            return;

        string nodetext = e.node.getdisplaytext("name");//参数填写fieldname  
        if (string.isnullorwhitespace(nodetext))
            return;

        bool isexist = nodetext.indexof(searchcontrol1.text, stringcomparison.ordinalignorecase) >= 0;
        if (isexist)
        {
            var node = e.node.parentnode;
            while (node != null)
            {
                if (!node.visible)
                {
                    node.visible = true;
                    node = node.parentnode;
                }
                else
                    break;
            }
        }
        e.node.visible = isexist;
        e.handled = true;
    };
}

上面只是初始化树列表控件,我们还需要对它进行数据的绑定显示,树列表的绑定主要代码就是数据绑定和图标的绑定。

this.tree.selectimagelist = this.imagecollection1;
this.tree.datasource = result.items;

不过我们图标由于是从数据源里面动态构建的,因此需要存在一个集合里面,方便赋值给树列表控件,如下是完整的绑定代码。

/// <summary>
/// 绑定树的数据源
/// </summary>
private async task bindtree()
{
    var pagedto = new menupageddto();
    var result = await menuapicaller.instance.getall(pagedto);

    //用来存放对应id和index顺序的
    idindexdict = new dictionary<string, int>();
    //重新刷新节点图片
    this.imagecollection1 = new imagecollection();
    this.imagecollection1.imagesize = new size(16, 16);
    if (result != null && result.items != null)
    {
        foreach (var menuinfo in result.items)
        {
            var image = loadicon(menuinfo);
            this.imagecollection1.images.add(image);
            
            //为每个菜单id添加一个图片序号,方便查找
            if(!idindexdict.containskey(menuinfo.id))
            {
                int index = this.imagecollection1.images.count - 1;//最后一个序号
                idindexdict.add(menuinfo.id, index);
            }
        }
    }
    //imagecollection变化了,需要重新赋值给treelist
    this.tree.selectimagelist = this.imagecollection1;
    this.tree.datasource = result.items;
    this.tree.expandall();
}

如果我们需要获取指定树节点的绑定的值,我们可以通过当前的focusenode获取字段值,如下代码所示。

        /// <summary>
        /// 编辑列表数据
        /// </summary>
        private async task edittreedata()
        {
            string id = string.concat(this.tree.focusednode.getvalue(id_fieldname));
            if (!string.isnullorempty(id))
            {
                frmeditmenu dlg = new frmeditmenu();
                dlg.id = id;
                dlg.ondatasaved += new eventhandler(dlg_ondatasaved);
                dlg.initfunction(loginuserinfo, functiondict);//给子窗体赋值用户权限信息

                if (dialogresult.ok == dlg.showdialog())
                {
                    await bindtree();
                }
            }
        }

以上就是treelist控件的使用过程,主要注意的就是数据源的绑定和图标的绑定操作即可。 

如果我们还允许树列表拖动,以便进行父位置的调整,那么可以对它进行一个拖动的事件处理。

   this.tree.calcnodedragimageindex += new devexpress.xtratreelist.calcnodedragimageindexeventhandler(this.tree_calcnodedragimageindex);
   this.tree.dragdrop += new system.windows.forms.drageventhandler(this.tree_dragdrop);
   this.tree.dragover += new system.windows.forms.drageventhandler(this.tree_dragover);
        private void tree_dragover(object sender, drageventargs e)
        {
            treelistnode dragnode = e.data.getdata(typeof(treelistnode)) as treelistnode;
            e.effect = getdragdropeffect(sender as treelist, dragnode);
        }

        private async void tree_dragdrop(object sender, drageventargs e)
        {
            treelistnode dragnode, targetnode;
            treelist tl = sender as treelist;
            point p = tl.pointtoclient(new point(e.x, e.y));

            dragnode = e.data.getdata(typeof(treelistnode)) as treelistnode;
            targetnode = tl.calchitinfo(p).node;

            //移动后更新数据
            var id = string.concat(dragnode.getvalue("id")).toint64();
            var pid = string.concat(targetnode.getvalue("id")).toint64();
            await organizationunitapicaller.instance.move(new moveorganizationunitdto() { id = id, parentid = pid });
            await bindtree();

            e.effect = dragdropeffects.none;
        }

        private void tree_calcnodedragimageindex(object sender, calcnodedragimageindexeventargs e)
        {
            treelist tl = sender as treelist;
            if (getdragdropeffect(tl, tl.focusednode) == dragdropeffects.none)
                e.imageindex = -1;  // no icon
            else
                e.imageindex = 1;  // the reorder icon (a curved arrow)
        }

在Winform开发框架中使用DevExpress的TreeList和TreeListLookupEdit控件

 

2、devexpress的treelistlookupedit控件使用

 treelist和treelistlookupedit一般都会成对出现,一般我们需要调整父节点的时候,都会涉及到这个树形的下拉列表treelistlookupedit控件的。

为了方便,我们一般都定义一个自定义控件来处理这些下拉列表的绑定,因此不需要每次都绑定数据以及初始化代码。

在Winform开发框架中使用DevExpress的TreeList和TreeListLookupEdit控件

        private void functioncontrol_load(object sender, eventargs e)
        {
            if (!this.designmode)
            {
                inittree();
                bindtree();
            }
        }

        /// <summary>
        /// 初始化树
        /// </summary>
        /// <returns></returns>
        private void inittree()
        {
            this.txtmenu.properties.valuemember = "id";
            this.txtmenu.properties.displaymember = "name";

            var tree = this.treelistlookupedit1treelist;
            tree.columns.clear();
            //控件扩展函数封装处理
            tree.createcolumn("name", "菜单名称", 160, true);
            tree.inittree("id", "pid", null, false, false);

            //设置树的图标集合及逐级图标
            tree.selectimagelist = this.imagecollection1;
            tree.customdrawnodeimages += (object sender, customdrawnodeimageseventargs e) =>
            {
                int maxcount = this.imagecollection1.images.count;
                var index = e.node.level < maxcount ? e.node.level : 0;
                e.selectimageindex = index;
            };
        }

        /// <summary>
        /// 绑定树列表
        /// </summary>
        /// <returns></returns>
        public void bindtree()
        {
            var tree = this.treelistlookupedit1treelist;

            //绑定列表数据
            var result = asynccontext.run(() => menuapicaller.instance.getall(new menupageddto() { }));
            if (result != null && result.items != null)
            {
                tree.datasource = result.items;
                tree.expandall();//展开所有
            }

            //执行绑定后处理
            if (databinded != null)
            {
                databinded(null, null);
            }
        }

其中 asynccontext.run 是把异步函数当做同步使用,一般我们在用户控件上,我们都使用同步操作,避免数据绑定延迟导致没有处理

通过制定valuemember和displaymember就可以正常显示内容,以及在后面存储需要的值操作。

  this.txtmenu.properties.valuemember = "id";
  this.txtmenu.properties.displaymember = "name";

treelistlookupedit控件里面还是有一个treelist控件的,这个控件的操作和我们上面说的treelist控件操作完全一样,我们按树列表的方式初始化并绑定它即可。

界面效果如下所示。

在Winform开发框架中使用DevExpress的TreeList和TreeListLookupEdit控件

树列表控件展开如下所示。

在Winform开发框架中使用DevExpress的TreeList和TreeListLookupEdit控件