C# - 解決TreeView控件複選框聯動時鼠標點擊過快致使的顯示不正確的問題

今天我在試着作一個C#樹形視圖(TreeView)控件,要求在每一個節點前面添加一個可用於打勾的複選框,並要求複選框有上下級聯動的效果。如今在網上能查到挺多知足這類功能的代碼,本來我也覺得這是一件挺簡單的事情,不過實際狀況並不是如此。node

咱們創建一個C#窗體應用程序,主窗體取名FormMain,在裏面放置一個Dock爲Fill的TreeView控件treeTest。注意該控件的CheckBoxes屬性要設置爲True才能顯示覆選框。
c#

在FormMain中寫入代碼以下:windows

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace TreeViewCheckTest
{
    public partial class FormMain : Form
    {
        public FormMain()
        {
            InitializeComponent();
        }

        private void FormMain_Load(object sender, EventArgs e)
        {
            //生成測試數據
            TreeNode treeNode11 = new TreeNode("蜉蝣目");
            treeNode11.Nodes.Add("等蜉科");
            treeNode11.Nodes.Add("四節蜉科");
            treeNode11.Nodes.Add("扁蜉科");
            treeNode11.Nodes.Add("蜉蝣科");
            treeNode11.Nodes.Add("河花蜉科");

            TreeNode treeNode12 = new TreeNode("蜚蠊目");
            treeNode12.Nodes.Add("姬蠊科");
            treeNode12.Nodes.Add("碩蠊科");
            treeNode12.Nodes.Add("地鱉科");
            treeNode12.Nodes.Add("隱尾蠊科");

            TreeNode treeNode13 = new TreeNode("螳螂目");
            treeNode13.Nodes.Add("螳科");
            treeNode13.Nodes.Add("花螳科");
            treeNode13.Nodes.Add("錐頭螳科");
            treeNode13.Nodes.Add("細足螳科");

            TreeNode treeNode14 = new TreeNode("其餘類型昆蟲");

            TreeNode treeNode1 = new TreeNode("昆蟲綱");
            treeNode1.Nodes.AddRange(
                new TreeNode[] { treeNode11, treeNode12, treeNode13, treeNode14 });

            treeTest.Nodes.Add(treeNode1);
            treeTest.ExpandAll();
        }

        /// <summary>
        /// 關聯鎖
        /// </summary>
        bool needSetRelateCheck = true;

        /// <summary>
        /// 樹節點前複選框發生變化時觸發
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void treeViewEnhanced1_AfterCheck(object sender, TreeViewEventArgs e)
        {
            if (!needSetRelateCheck)
            {
                return;
            }

            //一、判斷當前操做節點勾仍是不勾
            //若是勾,當前節點下全部子節點都要勾上
            //若是不勾,下面子節點所有不勾
            TreeNode node = e.Node;
            if (node.Checked)
            {
                node.Nodes.OfType<TreeNode>().ToList().ForEach(x => x.Checked = true);
            }
            else
            {
                node.Nodes.OfType<TreeNode>().ToList().ForEach(x => x.Checked = false);
            }

            //二、若是當前節點被勾選,若是當前節點被勾選,則其上溯全部祖先節點都要勾
            //不然判斷當前節點全部兄弟節點是否有勾,有則父節點要勾,沒有則父節點不勾
            needSetRelateCheck = false; //爲本方法上鎖,確保連帶影響不會運行本事件中代碼
            if (node.Checked)
            {
                while (node.Parent != null)
                {
                    node = node.Parent;
                    node.Checked = true;
                }
            }
            else
            {
                while (node.Parent != null)
                {
                    node = node.Parent;
                    bool hasCheckedChild = false;
                    foreach (TreeNode child in node.Nodes)
                    {
                        if (child.GetHashCode() == e.Node.GetHashCode())
                        {
                            continue;
                        }
                        if (child.Checked)
                        {
                            hasCheckedChild = true;
                            break;
                        }
                    }
                    if (!hasCheckedChild)
                    {
                        node.Checked = false;
                    }
                    else
                    {
                        break;
                    }
                }
            }
            needSetRelateCheck = true; //爲本方法解鎖
        }
    }
}

原則上這段代碼知足如下功能:ide

  1. 勾選一個節點時,該節點的全部子節點都被勾選函數

  2. 取消勾選一個節點時,該節點的全部子節點都被取消勾選測試

  3. 勾選一個節點時,若是該節點的全部兄弟都被勾選,則該節點的父節點也應被勾選spa

  4. 取消勾選一個節點時,若是該節點的全部兄弟節點都未被勾選,則該節點的父節點也應被取消勾選code

運行後效果以下:orm

原本覺得這樣就行了,結果發生了意想不到的事情:事件

在我用鼠標點擊一個複選框時,若是點擊間隔時間太短(達到相似雙擊的速度),則TreeView的複選框會顯示出現沒法正確聯動的問題。

後來我上網查了一些資料,終於找到了一個大牛給出的緣由,參見:

https://social.msdn.microsoft.com/Forums/windows/en-US/9d717ce0-ec6b-4758-a357-6bb55591f956/possible-bug-in-net-treeview-treenode-checked-state-inconsistent?forum=winforms

大牛的答案中說這是一個WindowsVista版本的BUG,不過我如今的32位Win7也會出現此問題。

解決這個問題的方法是將TreeView封裝一層,並重寫一下WndProc函數:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace TreeViewCheckTest
{
    class TreeViewEnhanced : TreeView
    {
        protected override void WndProc(ref Message m)
        {
            if (m.Msg == 0x203) { m.Result = IntPtr.Zero; }
            else base.WndProc(ref m);
        }
    }
}

實現了TreeViewEnhanced類後,將FormMain中的TreeView控件替換爲咱們剛剛實現的TreeViewEnhanced就好啦!

END

相關文章
相關標籤/搜索