winform實現自定義摺疊面板控件

代碼文件:https://github.com/Caijt/CollapsePanelnode

 

最近在學習作winform,想實現一個系統導航菜單,系統菜單以模塊進行分組,菜單是樹型結構。git

效果相似舊版QQ的那種摺疊面板,就是垂直並排不少個模塊按鈕,按其中一個模塊就展開哪個模塊裏面樹型菜單,以下圖所示,我先把我實現後的效果展現出來github

一開始我覺得這麼常見的控件,winform裏面確定有,結果大失所望,竟然沒有,我剛學習winform,就遇到難題,好吧,那就學下怎麼自定義控件,反正遲早要學的。數組

其實這個控件實現起來仍是滿簡單的,沒有太複雜的知識,就是把Button控件跟TreeView組合起來,主要調整它們的Dock值,配合控件的BringToFront方法跟SendToBack方法數據結構

 

首先先定義個人菜單數據結構,其實就是一個很簡單的樹型結構,主要有個ParentId來代表各節點的父子關係,根節點就是模塊,根節點下面的子節點,就是模塊的菜單。ide

namespace CollapsePanelForm
{
    public class MenuData
    {
        public int Id { get; set; }
        //可爲空,當爲空時,說明當前節點是根節點
        public int? ParentId { get; set; }
        //模塊或菜單的名稱
        public string Name { get; set; }
        //這個是用於構建菜單對應Form控件的路徑的,能夠利用反射實現打開匹配路徑的Form控件
        public string Path { get; set; }
    }
}

 

下面是控件的完整代碼,已進行註釋,我相信大家看得明白的。佈局

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Linq;
using System.Windows.Forms;

namespace CollapsePanelForm
{
    public partial class CollapsePanel : UserControl
    {
        /// <summary>
        /// 這是菜單列表數據,控件公開的屬性必須定義如下這些特性,否則會出錯,提示未標記爲可序列化
        /// </summary>
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
        [Localizable(true)]
        [MergableProperty(false)]
        public List<MenuData> Menus { get; set; }
        /// <summary>
        /// 菜單雙擊事件
        /// </summary>
        public event EventHandler MenuDoubleClick;
        /// <summary>
        /// 模塊的按鈕列表
        /// </summary>
        private List<Button> headerButtons;
        /// <summary>
        /// 模塊下的菜單,每個模塊下面的菜單對應一個TreeView控件
        /// </summary>
        private List<TreeView> treeViews;
        /// <summary>
        /// 當前控件打開的模塊索引值
        /// </summary>
        private int? openMenuIndex = null;
        /// <summary>
        /// 當模塊處理打開狀態時,模塊名稱後帶的符號
        /// </summary>
        private string openArrow = " <<";
        /// <summary>
        /// 當模塊處理關閉狀態時,模塊名稱後帶的符號
        /// </summary>
        private string hideArrow = " >>";
        public CollapsePanel()
        {
            InitializeComponent();
            headerButtons = new List<Button>();
            treeViews = new List<TreeView>();
            Menus = new List<MenuData>();
            this.InitMenus();
        }
        /// <summary>
        /// 根據Menus的數據初始化控件,就是動態增長Button跟TreeView控件
        /// </summary>
        public void InitMenus()
        {
            this.Controls.Clear();
            //過濾出全部ParentId爲null的根節點,就是模塊列表
            foreach (var menu in Menus.Where(a => a.ParentId == null))
            {
                Button headerButton = new Button();
                headerButton.Dock = DockStyle.Top;
                headerButton.Tag = menu.Name;
                headerButton.Text = menu.Name + hideArrow;
                headerButton.TabStop = false;
                headerButton.Click += headerButton_Click;
                headerButtons.Add(headerButton);

                this.Controls.Add(headerButton);
                //這個BringToFront置於頂層方法對於佈局很重要
                headerButton.BringToFront();

                TreeView tree = new TreeView();
                //用一個遞歸方法構建出nodes節點
                tree.Nodes.AddRange(buildTreeNode(menu.Id, menu.Path.Substring(0, 1).ToUpper() + menu.Path.Substring(1)));
                tree.Visible = false;
                tree.Dock = DockStyle.Fill;
                tree.NodeMouseDoubleClick += Tree_DoubleClick;
                treeViews.Add(tree);
                this.Controls.Add(tree);
            }
        }

        private void Tree_DoubleClick(object sender, EventArgs e)
        {
            if (MenuDoubleClick != null)
            {
                MenuDoubleClick(sender, e);
            }
        }
        /// <summary>
        /// 模塊按鈕單擊事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void headerButton_Click(object sender, EventArgs e)
        {
            var clickButton = sender as Button;
            //得出當前單擊的模塊按鈕索引值
            var clickMenuIndex = headerButtons.IndexOf(clickButton);
            //若是當前單擊的模塊按鈕索引值等於已經打開的模塊索引值的話,那麼當前模塊要關閉,不然則打開
            if (openMenuIndex == clickMenuIndex)
            {
                clickButton.Text = clickButton.Tag.ToString() + hideArrow;
                this.treeViews[clickMenuIndex].Hide();
                openMenuIndex = null;
            }
            else
            {
                //關閉以前打開的模塊按鈕
                if (openMenuIndex.HasValue)
                {
                    this.treeViews[openMenuIndex.Value].Hide();
                    headerButtons[openMenuIndex.Value].Text = headerButtons[openMenuIndex.Value].Tag.ToString() + hideArrow;
                }
                clickButton.Text = clickButton.Tag.ToString() + openArrow;
                openMenuIndex = clickMenuIndex;
                this.treeViews[clickMenuIndex].Show();
            }
            //如下的操做也很重要,根據當前單擊的模塊按鈕索引值,小於這個值的模塊按鈕移到上面,大於的移到下面
            int i = 0;
            foreach (var b in headerButtons)
            {
                if (i <= clickMenuIndex || openMenuIndex == null)
                {
                    b.Dock = DockStyle.Top;
                    b.BringToFront();

                }
                else
                {
                    b.Dock = DockStyle.Bottom;
                    b.SendToBack();
                }
                i++;
            }
            //最後對應的TreeView控件得置於頂層,這樣佈局就完美了
            this.treeViews[clickMenuIndex].BringToFront();
        }
        /// <summary>
        /// 遞歸根據節點的Id,構建出TreeNode數組,這個prefixPath是用來構建完美的Path路徑的
        /// </summary>
        /// <param name="parentId"></param>
        /// <param name="prefixPath"></param>
        /// <returns></returns>
        private TreeNode[] buildTreeNode(int parentId, string prefixPath)
        {
            List<TreeNode> nodeList = new List<TreeNode>();
            Menus.ForEach(m =>
            {
                if (m.ParentId == parentId)
                {
                    //拼接當前節點完整路徑,而後再傳給遞歸方法
                    string path = prefixPath + "." + m.Path.Substring(0, 1).ToUpper() + m.Path.Substring(1);
                    TreeNode node = new TreeNode();
                    node.Text = m.Name;
                    node.Tag = path;
                    node.Nodes.AddRange(buildTreeNode(m.Id, path));
                    nodeList.Add(node);

                }
            });
            return nodeList.ToArray();
        }
    }
}
相關文章
相關標籤/搜索