Winform中如何实现下拉树效果(类似于ComboBox下拉时显示的是树状结构)

Winform中如何实现下拉树效果

简介:Winform中如何实现下拉树效果(类似于ComboBox下拉时显示的是树状结构),

        如果是BS的WebForm就有很多解决方案了,Devexpress就更方便,利用DropdownList和TreeView去组合控件实现

        但是CS中的Winform的传统窗体控件和Devexpress里面去实现此功能就比较麻烦了

        而此文,我们要讲解的就是如何利用Winform的传统用户控件去实现下拉树的效果

       主要利用TextBox,TreeView,Button等控件事件组合而成类似于ComboBox下拉出现树状结构

       下面就看看我们要实现的下拉树的效果图:

说明:上图所示的就是我们要实现的效果当点击下拉控件时,出现一个树状结构,

         当我们选择树节点时(双击),把相应的节点值,放入上面的文本框中

         好了,下面我们就马上去看看,这样的功能是如何实现的呢?

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------

首先我们需要建立一个用户控件,如图:

说明:不需要再进行任何操作

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------

接下来,我们需要放一张图片文件,这张图片文件是为了模拟下拉框右边的三角下拉按钮用的,如图:

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------

接下来就是完全的代码模块了,就这样的界面操作,是如何让我们做到下拉树效果的呢?

在用户控件窗体CS文件中,我们需要添加如下代码:

一.自定义成员变量

二.内部辅助方法

三.对TextBox的事件处理

四.对TreeView的事件处理

五.对Button的事件处理

六.对自身的事件处理

七.外部属性封装

八.构造函数

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------

别看上面说的步骤很多,其实加起来也就几百行代码,也没有什么难点,接下来我们就依次展示我们八大步骤模块的代码:

一.自定义成员变量 

        #region 自定义成员变量

        private TextBox m_TextBox;
        private TreeView m_TreeView;
        private Button m_DropdownButton;
        private int m_MaxDropDownItems = 8;
        //是否正在显示下拉列表
        private bool b_Dropdown = false;
        //使能
        private bool b_Enabled = true;
        //事件
        public event EventHandler DropDown;
        public event EventHandler DropDownClosed;
        public event EventHandler EnableChanged;
        public event TreeViewCancelEventHandler BeforeExpand;
        public event TreeViewCancelEventHandler BeforeCollapse;
        public event TreeViewEventHandler AfterExpand;
        public event TreeViewEventHandler AfterCollapse;

        #endregion

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------

二.内部辅助方法

        #region 内部辅助方法

        /// <summary>
        /// 创建并初始化所有控件,包括添加事件处理函数
        /// </summary>
        /// <author>PengZhen</author>
        /// <time>2013-11-1 13:29:37</time>
        private void InitControls()
        {
            //TextBox
            this.m_TextBox = new TextBox();
            this.m_TextBox.KeyDown += new KeyEventHandler(m_TextBox_KeyDown);
            this.m_TextBox.Parent = this;

            //Button
            this.m_DropdownButton = new Button();
            //Assembly asm = Assembly.GetExecutingAssembly();
            //System.IO.Stream stream = asm.GetManifestResourceStream(asm.GetName().Name + ".BM_dropdown.bmp");
            Bitmap bm = new Bitmap(this.GetType(), "BM_dropdown.bmp");
            this.m_DropdownButton.Image = bm;
            this.m_DropdownButton.Width = 16;
            this.m_DropdownButton.Height = this.m_TextBox.Height - 2;
            this.m_DropdownButton.Location = new Point(this.m_TextBox.Right - 18, 1);
            this.m_DropdownButton.Click += new EventHandler(m_DropdownButton_Click);
            this.m_DropdownButton.FlatStyle = FlatStyle.Flat;
            this.m_DropdownButton.Parent = this;
            this.m_DropdownButton.BringToFront();

            //TreeView
            this.m_TreeView = new TreeView();
            this.m_TreeView.Visible = false;
            this.m_TreeView.DoubleClick += new EventHandler(m_TreeView_DoubleClick);
            this.m_TreeView.KeyDown += new KeyEventHandler(m_TreeView_KeyDown);
            this.m_TreeView.LostFocus += new EventHandler(m_TreeView_LostFocus);
            this.m_TreeView.BeforeExpand += new TreeViewCancelEventHandler(m_TreeView_BeforeExpand);
            this.m_TreeView.BeforeCollapse += new TreeViewCancelEventHandler(TreeComboBox_BeforeCollapse);
            this.m_TreeView.AfterExpand += new TreeViewEventHandler(m_TreeView_AfterExpand);
            this.m_TreeView.AfterCollapse += new TreeViewEventHandler(TreeComboBox_AfterCollapse);
            this.m_TreeView.Location = new Point(0, 0);
            this.m_TreeView.Parent = null;

            this.LostFocus += new EventHandler(TreeComboBox_LostFocus);
        }

        /// <summary>
        /// 布局所有控件,让TextBox尺寸适应容器尺寸
        /// </summary>
        /// <author>PengZhen</author>
        /// <time>2013-11-1 13:29:37</time>
        private void LayoutControls()
        {
            this.m_TextBox.Width = this.Width;
            this.Height = this.m_TextBox.Height;
            this.m_DropdownButton.Left = this.m_TextBox.Right - 18;
            this.m_DropdownButton.Height = this.m_TextBox.Height - 2;
            this.m_TreeView.Width = this.Width;
            this.m_TreeView.Height = (int) ((this.Font.Height + 3)*this.m_MaxDropDownItems);
        }

        /// <summary>
        /// 显示下拉列表
        /// </summary>
        /// <author>PengZhen</author>
        /// <time>2013-11-1 13:29:37</time>
        private void ShowDropDown()
        {
            if (this.Parent == null)
                return;

            // 智能计算显示的位置,尝试在下方显示,如果没有足够空间,则在上方显示
            // 尝试在下方的位置(现在只是相对父窗口的相对位置)
            Point pos = new Point(this.Left, this.Bottom - 1);

            // 把位置映射到顶层窗口,获取父窗口的屏幕坐标
            Point parentPos = this.Parent.PointToScreen(this.Parent.Location);
            // 获取顶层窗口的屏幕坐标
            Point topParentPos = this.TopLevelControl.PointToScreen(this.Parent.Location);
            // 把相对父窗口的位置变换为相对顶级窗口的位置,因为popup的父是顶级窗口
            pos.Offset(parentPos.X - topParentPos.X, parentPos.Y - topParentPos.Y);

            // 检查是否有足够空间用于在label下方显示day picker
            if ((pos.Y + this.m_TreeView.Height) > this.TopLevelControl.ClientRectangle.Height)
            {
                // 没有足够的空间(超出了顶级窗口客户区),尝试在上方显示将Y方向,向上平移
                pos.Y -= (this.Height + this.m_TreeView.Height);
                if (pos.Y < 0)
                {
                    // 如果上方仍然没有空间显示,则显示在顶级窗口的底部
                    pos.Y = (this.TopLevelControl.ClientRectangle.Height - this.m_TreeView.Height);
                }
            }

            // 尝试停靠,如果右边超过顶级窗口的客户区,则将控件向左移动,并紧靠在顶级窗口右侧
            if ((pos.X + this.m_TreeView.Width) > this.TopLevelControl.ClientRectangle.Width)
                pos.X = (this.TopLevelControl.ClientRectangle.Width - this.m_TreeView.Width);

            this.m_TreeView.Location = pos; // this.Parent.PointToScreen(pt);
            this.m_TreeView.Visible = true;
            this.m_TreeView.Parent = this.TopLevelControl;
            this.m_TreeView.BringToFront();
            this.b_Dropdown = true;
            //raise event
            if (this.DropDown != null)
                this.DropDown(this, EventArgs.Empty);
            this.m_TreeView.Focus();
        }

        /// <summary>
        /// 隐藏下拉列表
        /// </summary>
        /// <author>PengZhen</author>
        /// <time>2013-11-1 13:29:37</time>
        private void HideDropDown()
        {
            if (this.DropDownClosed != null)
                this.DropDownClosed(this, EventArgs.Empty);
            this.m_TreeView.Parent = null;
            this.m_TreeView.Visible = false;
            this.b_Dropdown = false;
        }

        #endregion

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------
三.对TextBox的事件处理

        #region 事件处理 - TextBox

        /// <summary>
        /// 在编辑框中按下按键
        /// </summary>
        /// <author>PengZhen</author>
        /// <time>2013-11-1 13:29:37</time>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void m_TextBox_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyCode == Keys.Enter)
            {
                if (this.b_Dropdown)
                    this.HideDropDown();
                else
                    this.ShowDropDown();
            }
            else if (e.KeyCode == Keys.Down)
            {
                this.ShowDropDown();
                this.m_TreeView.Focus();
            }
        }

        #endregion

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------

四.对TreeView的事件处理

        #region 事件处理 - TreeView

        /// <summary>
        /// 在下拉列表中选择了一个节点的事件处理!
        /// </summary>
        /// <author>PengZhen</author>
        /// <time>2013-11-1 13:29:37</time>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void m_TreeView_KeyDown(object sender, KeyEventArgs e)
        {
            //如果按下回车表示要选中当前节点
            if (e.KeyCode == Keys.Enter)
            {
                this.m_TreeView_DoubleClick(sender, EventArgs.Empty);
            }
        }

        /// <summary>
        /// 失去焦点时,如果不是被下拉按钮夺取的焦点,则隐藏它!
        /// </summary>
        /// <author>PengZhen</author>
        /// <time>2013-11-1 13:29:37</time>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void m_TreeView_LostFocus(object sender, EventArgs e)
        {
            if (!this.m_DropdownButton.Focused)
                this.HideDropDown();
        }

        /// <summary>
        /// 在下拉列表中双击
        /// </summary>
        /// <author>PengZhen</author>
        /// <time>2013-11-1 13:29:37</time>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void m_TreeView_DoubleClick(object sender, EventArgs e)
        {
            TreeNode node = this.m_TreeView.SelectedNode;
            if (node != null)
                this.m_TextBox.Text = node.Text;

            if (this.b_Dropdown)
            {
                this.HideDropDown();
            }
        }

        /// <summary>
        /// 折叠后事件
        /// </summary>
        /// <author>PengZhen</author>
        /// <time>2013-11-1 13:29:37</time>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void TreeComboBox_AfterCollapse(object sender, TreeViewEventArgs e)
        {
            if (this.AfterCollapse != null)
                this.AfterCollapse(this, e);
        }

        /// <summary>
        /// 折叠前事件
        /// </summary>
        /// <author>PengZhen</author>
        /// <time>2013-11-1 13:29:37</time>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void TreeComboBox_BeforeCollapse(object sender, TreeViewCancelEventArgs e)
        {
            if (this.BeforeCollapse != null)
                this.BeforeCollapse(this, e);
        }

        /// <summary>
        /// 展开后事件
        /// </summary>
        /// <author>PengZhen</author>
        /// <time>2013-11-1 13:29:37</time>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void m_TreeView_AfterExpand(object sender, TreeViewEventArgs e)
        {
            if (this.AfterExpand != null)
                this.AfterExpand(this, e);
        }

        /// <summary>
        /// 展开前事件
        /// </summary>
        /// <author>PengZhen</author>
        /// <time>2013-11-1 13:29:37</time>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void m_TreeView_BeforeExpand(object sender, TreeViewCancelEventArgs e)
        {
            if (this.BeforeExpand != null)
                this.BeforeExpand(this, e);
        }


        #endregion

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------
五.对Button的事件处理

        #region 事件处理 - Button

        /// <summary>
        /// 下拉按钮点击事件
        /// </summary>
        /// <author>PengZhen</author>
        /// <time>2013-11-1 13:29:37</time>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void m_DropdownButton_Click(object sender, EventArgs e)
        {
            //throw new Exception("The method or operation is not implemented.");
            if (this.b_Dropdown)
                this.HideDropDown();
            else
                this.ShowDropDown();
        }

        #endregion

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------

六.对自身的事件处理

        #region 事件处理 - 自身

        /// <summary>
        /// 失去焦点
        /// </summary>
        /// <author>PengZhen</author>
        /// <time>2013-11-1 13:29:37</time>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void TreeComboBox_LostFocus(object sender, EventArgs e)
        {
            if (this.b_Dropdown)
                this.HideDropDown();
        }

        /// <summary>
        /// 重设尺寸
        /// </summary>
        /// <author>PengZhen</author>
        /// <time>2013-11-1 13:29:37</time>
        /// <param name="e"></param>
        protected override void OnResize(EventArgs e)
        {
            base.OnResize(e);
            this.LayoutControls();
        }

        /// <summary>
        /// 尺寸改变
        /// </summary>
        /// <author>PengZhen</author>
        /// <time>2013-11-1 13:29:37</time>
        /// <param name="e"></param>
        protected override void OnSizeChanged(EventArgs e)
        {
            base.OnSizeChanged(e);
            this.LayoutControls();
        }

        #endregion

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------

七.外部属性封装

        #region 外部属性封装

        /// <summary>
        /// 获取节点集合
        /// </summary>
        /// <author>PengZhen</author>
        /// <time>2013-11-1 13:29:37</time>
        public TreeNodeCollection Nodes
        {
            get { return this.m_TreeView.Nodes; }
        }

        /// <summary>
        /// 设置或者获取节点的图片列表
        /// </summary>
        /// <author>PengZhen</author>
        /// <time>2013-11-1 13:29:37</time>
        public ImageList ImageList
        {
            get { return this.m_TreeView.ImageList; }
            set { this.m_TreeView.ImageList = value; }
        }

        /// <summary>
        /// 重写enabled属性
        /// </summary>
        /// <author>PengZhen</author>
        /// <time>2013-11-1 13:29:37</time>
        public new bool Enabled
        {
            get { return this.b_Enabled; }
            set
            {
                if (this.b_Enabled != value)
                {
                    this.b_Enabled = value;
                    this.m_DropdownButton.Enabled = value;
                    this.b_Enabled = value;
                    //当灰掉时,隐藏下拉列表
                    if (!this.b_Enabled && this.b_Dropdown)
                        this.HideDropDown();
                    if (this.EnableChanged != null)
                        this.EnableChanged(this, EventArgs.Empty);
                }
            }
        }

        /// <summary>
        /// 字体
        /// </summary>
        /// <author>PengZhen</author>
        /// <time>2013-11-1 13:29:37</time>
        public override Font Font
        {
            get { return base.Font; }
            set
            {
                base.Font = value;
                this.m_TextBox.Font = value;
                this.m_TreeView.Font = value;
                //调整布局
                this.LayoutControls();
            }
        }

        /// <summary>
        /// 重写Text
        /// </summary>
        /// <author>PengZhen</author>
        /// <time>2013-11-1 13:29:37</time>
        public override string Text
        {
            get { return this.m_TextBox.Text; }
            set
            {
                if (this.Text != value)
                {
                    this.m_TextBox.Text = value;
                }
            }
        }

        /// <summary>
        /// 是否显示lines
        /// </summary>
        /// <author>PengZhen</author>
        /// <time>2013-11-1 13:29:37</time>
        public bool ShowLines
        {
            get { return this.m_TreeView.ShowLines; }
            set { this.m_TreeView.ShowLines = value; }
        }

        /// <summary>
        /// 是否显示+ -按钮
        /// </summary>
        /// <author>PengZhen</author>
        /// <time>2013-11-1 13:29:37</time>
        public bool ShowPlusMinus
        {
            get { return this.m_TreeView.ShowPlusMinus; }
            set { this.m_TreeView.ShowPlusMinus = value; }
        }

        /// <summary>
        /// 是否显示root lines
        /// </summary>
        /// <author>PengZhen</author>
        /// <time>2013-11-1 13:29:37</time>
        public bool ShowRootLines
        {
            get { return this.m_TreeView.ShowRootLines; }
            set { this.m_TreeView.ShowRootLines = value; }
        }

        /// <summary>
        /// 是否显示root lines
        /// </summary>
        /// <author>PengZhen</author>
        /// <time>2013-11-1 13:29:37</time>
        public bool ShowNodeToolTips
        {
            get { return this.m_TreeView.ShowNodeToolTips; }
            set { this.m_TreeView.ShowNodeToolTips = value; }
        }

        /// <summary>
        /// 获取或者设置选中的节点!
        /// </summary>
        /// <author>PengZhen</author>
        /// <time>2013-11-1 13:29:37</time>
        public TreeNode SelectedNode
        {
            get { return this.m_TreeView.SelectedNode; }
            set
            {
                this.m_TreeView.SelectedNode = value;
                if (value != null)
                    this.Text = value.Text;
            }
        }

        /// <summary>
        /// 设置下拉最多项
        /// </summary>
        /// <author>PengZhen</author>
        /// <time>2013-11-1 13:29:37</time>
        public int MaxDropDownItems
        {
            get { return this.m_MaxDropDownItems; }
            set
            {
                if (this.m_MaxDropDownItems != value)
                {
                    this.m_MaxDropDownItems = value;
                    this.m_TreeView.Height = this.m_TextBox.Height*value;
                }
            }
        }

        #endregion

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------

八.构造函数

        /// <summary>
        /// 构造函数
        /// </summary>
        public TreeComboBox()
        {
            this.InitControls();
            this.LayoutControls();
        }

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------

OK,经过上面八个步骤,我们的下拉树自定义控件就完成了,接下来,就看下我们是如何使用它的

首先我们需要生成一下项目,成功后我们将在我们的工具栏中,看到如下控件:

说明:做到此处出现错误时,请参照文章尾部提出的解决方案...

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------

接下来我们就需要把该控件拖到我们的窗体中

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------

这些完成的之后,接下来我们就是我们真正的代码调用模块了,我们需要在调用下拉树控件窗体中添加如下代码:

首先是绑定数据:

        /// <summary>
        /// 数据分类数据绑定
        /// </summary>
        /// <author>PengZhen</author>
        /// <time>2013-11-1 14:22:35</time>
        private void BindDataType()
        {
            for (int i = 0; i < 4; i++)
            {
                tcbDataType.Nodes.Add("key" + i, "Depart" + i, 0, 0);
                for (int j = 0; j < i + 1; j++)
                {
                    tcbDataType.Nodes[i].Nodes.Add("key_child" + i + j, "User" + i + j, 2, 2);
                }
            }
        }

说明:此处的for循环代表树的级数,在此我们可以看出,此代码将会生成一个两级树,如果我们想出现多级,只需要加入多个循环,就可以了

         如果您嫌麻烦,可以封装一下通用方法

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------

其次是添加事件:

1.添加折叠事件

        /// <summary>
        /// 下拉树折叠后事件
        /// </summary>
        /// <author>PengZhen</author>
        /// <time>2013-11-1 14:18:29</time>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void tcbDataType_AfterCollapse(object sender, TreeViewEventArgs e)
        {
            if (e.Node.ImageIndex == 1)
                e.Node.ImageIndex = e.Node.SelectedImageIndex = 0;
        }

2.添加展开事件

        /// <summary>
        /// 下拉树展开后事件
        /// </summary>
        /// <author>PengZhen</author>
        /// <time>2013-11-1 14:18:29</time>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void tcbDataType_AfterExpand(object sender, TreeViewEventArgs e)
        {
   	   if (e.Node.ImageIndex == 0)
    	       e.Node.ImageIndex = e.Node.SelectedImageIndex = 1;
        }

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------
最后,我们需要注册事件:

    可以在调用窗体的构造函数中加入如下代码: 

            this.tcbDataType.AfterExpand += new TreeViewEventHandler(tcbDataType_AfterExpand);
            this.tcbDataType.AfterCollapse += new TreeViewEventHandler(tcbDataType_AfterCollapse);


    或者查看下拉树控件的属性,在相应的事件中,选择我们编写的事件即可,如下图:

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------

好了,到此,我们的下拉树就完成了,让我们看下效果,如图:

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------

细心的人一定会发现,不对啊,这个数结构和文章的第一张图里面不一样啊,是的,因为少了节点图片,

我们做的这么久还没有给节点加图片的操作呢,好了,接下来,就是我最后的操作了

给树节点加图片操作,分三步

1.拖一个ImageList控件到窗体中

2.给ImageList添加图片列表,如图

3.将我们的下拉树控件中的属性ImageList设置成我们刚拖进来的ImageList控件,就可以了

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------

好的,做到这,我们的下拉树控件的一个完整效果就完成了,运行之后,我们就会看见文章刚出现的效果图的画面了

不过,做到这,我们还要最后一个收尾操作,那就是获取下拉树中用户选择的节点键和值

代码如下:

        /// <summary>
        /// 查询
        /// </summary>
        /// <author>PengZhen</author>
        /// <time>2013-10-30 11:08:03</time>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btSelect_Click(object sender, EventArgs e)
        {
            string value = tcbDataType.SelectedNode.Name;

            MessageBox.Show(value);

            string text = tcbDataType.Text.Trim();

            MessageBox.Show(text);
        }

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------

OK,OK,终于,整个下拉树的完整操作就全部实现了,有木有发现很简单呢,嘿嘿

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------

备注:此控件请放在窗体中使用,别放在用户控件中使用,至于为什么,您可以试试,就会出现意想不到的效果噢,

         而出现此问题,就留给你们自己去思考,解决吧,多去试试,变通思想,会出现更奇妙的事噢,

         好了,就写到这,让你们自己去见证奇迹的时刻把,嘿嘿...
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------

写点题外话,当你们去完成以上操作时,可能会出现以下错误,去看看下面的链接,就知道怎么去解决了

System.ArgumentException无法在类中找到资源 

 


版权声明:本文为pengzhen8805原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。